diff --git a/app/Http/Controllers/ShowDocumentationController.php b/app/Http/Controllers/ShowDocumentationController.php index bae2ce42..d4faeba2 100644 --- a/app/Http/Controllers/ShowDocumentationController.php +++ b/app/Http/Controllers/ShowDocumentationController.php @@ -46,7 +46,6 @@ public function __invoke(Request $request, string $platform, string $version, ?s } catch (InvalidArgumentException $e) { return $this->redirectToFirstNavigationPage($navigation, $page); } - SEOTools::setTitle($pageProperties['title'].' - NativePHP '.$platform.' v'.$version); SEOTools::setDescription(Arr::exists($pageProperties, 'description') ? $pageProperties['description'] : ''); @@ -65,7 +64,8 @@ protected function getPageProperties($platform, $version, $page = null): array $pageProperties = $document->matter(); $versionProperties = YamlFrontMatter::parseFile(resource_path("views/docs/{$platform}/{$version}/_index.md")); - $pageProperties = array_merge($pageProperties, $versionProperties->matter()); + + $pageProperties = array_merge($versionProperties->matter(), $pageProperties); $pageProperties['platform'] = $platform; $pageProperties['version'] = $version; diff --git a/resources/views/docs/mobile/1/_index.md b/resources/views/docs/mobile/1/_index.md index e69de29b..68ac888e 100644 --- a/resources/views/docs/mobile/1/_index.md +++ b/resources/views/docs/mobile/1/_index.md @@ -0,0 +1,99 @@ +--- +title: Mobile +order: 1 +--- + +# NativePHP for Mobile + +**Build native iOS and Android apps with PHP and Laravel** + +NativePHP for Mobile revolutionizes mobile development by allowing PHP developers to create native mobile applications using the languages and frameworks they already know and love. No need to learn Swift, Kotlin, or React Native - just pure PHP and Laravel. + +## What Makes NativePHP Mobile Special? + +πŸ“±**Native Performance** - Your app runs natively on device with embedded PHP runtime +πŸ”₯**True Mobile APIs** - Access camera, biometrics, push notifications, and more +⚑ **Laravel Powered** - Use your existing Laravel skills and ecosystem +🚫**No Web Server** - Your app runs entirely on-device +πŸ”„**Cross Platform** - Single codebase for iOS and Android + +## Quick Start + +Get your first mobile app running in minutes: + +```bash +# Install NativePHP Mobile +composer require nativephp/mobile + +# Configure your app +php artisan native:install + +# Run your app +php artisan native:run +``` + +## Current Features (v1.1) + +**Available now:** +- πŸ“· Camera & Gallery access +- πŸ” Biometric authentication (Face ID, Touch ID, Fingerprint) +- πŸ”” Push notifications via Firebase +- πŸ’¬ Native dialogs & toasts +- πŸ”— Deep links & universal links +- πŸ“± NFC support +- πŸ“³ Haptic feedback & vibration +- πŸ”¦ Flashlight control +- πŸ“€ Native sharing +- πŸ”’ Secure storage (Keychain/Keystore) +- πŸ“ Location services + +[See the complete roadmap πŸ—ΊοΈ](/docs/mobile/1/getting-started/roadmap) + +## Documentation Sections + +### [Getting Started](/docs/mobile/1/getting-started) +Everything you need to start building mobile apps with PHP: +- [Introduction](/docs/mobile/1/getting-started/introduction) - Learn how NativePHP Mobile works +- [Installation](/docs/mobile/1/getting-started/installation) - Set up your development environment +- [Environment Setup](/docs/mobile/1/getting-started/environment-setup) - Configure iOS and Android tools +- [Configuration](/docs/mobile/1/getting-started/configuration) - App settings and permissions +- [Development](/docs/mobile/1/getting-started/development) - Development workflow +- [Roadmap](/docs/mobile/1/getting-started/roadmap) - Current and planned features + +### [The Basics](/docs/mobile/1/the-basics) +Core concepts and fundamental knowledge: +- [Native Functions](/docs/mobile/1/the-basics/native-functions) - Understanding sync vs async APIs +- [Asynchronous Methods](/docs/mobile/1/the-basics/asynchronous-methods) - Event-driven mobile development +- [ICU Support](/docs/mobile/1/the-basics/icu-support) - International components for Unicode + +### [Concepts](/docs/mobile/1/concepts) +Important concepts for mobile app development: +- [CI/CD](/docs/mobile/1/concepts/ci-cd) - Continuous integration and deployment +- [Deep Links](/docs/mobile/1/concepts/deep-links) - Universal links, app links, and NFC +- [Push Notifications](/docs/mobile/1/concepts/push-notifications) - Firebase Cloud Messaging setup +- [Splash Screen/Icons](/docs/mobile/1/concepts/splash-screen-icons) - App branding and assets +- [Versioning](/docs/mobile/1/concepts/versioning) - App version management + +### [APIs](/docs/mobile/1/apis) +Complete API reference for all native features: +- [Biometrics](/docs/mobile/1/apis/biometrics) - Face ID, Touch ID, fingerprint authentication +- [Camera](/docs/mobile/1/apis/camera) - Photo capture and gallery access +- [Dialog](/docs/mobile/1/apis/dialog) - Alerts, toasts, and sharing +- [Geolocation](/docs/mobile/1/apis/geolocation) - GPS and location services +- [Haptics](/docs/mobile/1/apis/haptics) - Vibration and tactile feedback +- [PushNotifications](/docs/mobile/1/apis/push-notifications) - FCM token management +- [SecureStorage](/docs/mobile/1/apis/secure-storage) - Keychain and keystore operations +- [System](/docs/mobile/1/apis/system) - Flashlight and legacy methods + +### [Digging Deeper](/docs/mobile/1/digging-deeper) +Advanced topics for production apps: +- [Databases](/docs/mobile/1/digging-deeper/databases) - SQLite and data management +- [Security](/docs/mobile/1/digging-deeper/security) - Best practices and secure storage + +## Need Help? + +- **License Required** - [Purchase your license](https://nativephp.com/mobile) to get started +- **Community** - Join our Discord for support and discussions +- **Examples** - Check out the Kitchen Sink demo app (coming soon to app stores) + +Ready to build your first mobile app with PHP? [Let's get started! πŸš€](/docs/mobile/1/getting-started/introduction) diff --git a/resources/views/docs/mobile/1/apis/_index.md b/resources/views/docs/mobile/1/apis/_index.md new file mode 100644 index 00000000..31293365 --- /dev/null +++ b/resources/views/docs/mobile/1/apis/_index.md @@ -0,0 +1,4 @@ +--- +title: APIs +order: 4 +--- diff --git a/resources/views/docs/mobile/1/apis/biometrics.md b/resources/views/docs/mobile/1/apis/biometrics.md new file mode 100644 index 00000000..e3f4ed2e --- /dev/null +++ b/resources/views/docs/mobile/1/apis/biometrics.md @@ -0,0 +1,93 @@ +--- +title: Biometrics +order: 100 +--- + +## Overview + +The Biometrics API allows you to authenticate users using their device's biometric sensors like Face ID, Touch ID, or fingerprint scanners. + +```php +use Native\Mobile\Facades\Biometrics; +``` + +## Methods + +### `promptForBiometricID()` + +Prompts the user for biometric authentication. + +## Events + +### `Completed` + +Fired when biometric authentication completes (success or failure). + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Biometric\Completed; + +#[On('native:' . Completed::class)] +public function handleBiometricAuth(bool $success) +{ + if ($success) { + // User authenticated successfully + $this->unlockSecureFeature(); + } else { + // Authentication failed + $this->showErrorMessage(); + } +} +``` + +## Example Usage + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\Biometrics; +use Native\Mobile\Events\Biometric\Completed; + +class SecureArea extends Component +{ + public bool $isUnlocked = false; + public bool $isAuthenticating = false; + + public function authenticate() + { + $this->isAuthenticating = true; + Biometrics::promptForBiometricID(); + } + + #[On('native:' . Completed::class)] + public function handleBiometricAuth(bool $success) + { + $this->isAuthenticating = false; + + if ($success) { + $this->isUnlocked = true; + session(['biometric_authenticated' => true]); + } else { + $this->addError('auth', 'Biometric authentication failed'); + } + } + + public function render() + { + return view('livewire.secure-area'); + } +} +``` + +## Platform Support + +- **iOS:** Face ID, Touch ID +- **Android:** Fingerprint, Face unlock, other biometric methods +- **Fallback:** System authentication (PIN, password, pattern) + +## Security Notes + +- Biometric authentication provides **convenience**, not absolute security +- Always combine with other authentication factors for sensitive operations +- Consider implementing session timeouts for unlocked states +- Users can potentially bypass biometrics if their device is compromised diff --git a/resources/views/docs/mobile/1/apis/camera.md b/resources/views/docs/mobile/1/apis/camera.md new file mode 100644 index 00000000..da2f750c --- /dev/null +++ b/resources/views/docs/mobile/1/apis/camera.md @@ -0,0 +1,161 @@ +--- +title: Camera +order: 200 +--- + +## Overview + +The Camera API provides access to the device's camera for taking photos and selecting images from the gallery. + +```php +use Native\Mobile\Facades\Camera; +``` + +## Methods + +### `getPhoto()` + +Opens the camera interface to take a photo. + +```php +Camera::getPhoto(); +``` + +### `pickImages()` + +Opens the gallery/photo picker to select existing images. + +**Parameters:** +- `string $media_type` - Type of media to pick: `'all'`, `'images'`, `'videos'` (default: `'all'`) +- `bool $multiple` - Allow multiple selection (default: `false`) + +**Returns:** `bool` - `true` if picker opened successfully + +```php +// Pick a single image +Camera::pickImages('images', false); + +// Pick multiple images +Camera::pickImages('images', true); + +// Pick any media type +Camera::pickImages('all', true); +``` + +## Events + +### `PhotoTaken` + +Fired when a photo is taken with the camera. + +**Payload:** `string $path` - File path to the captured photo + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Camera\PhotoTaken; + +#[On('native:' . PhotoTaken::class)] +public function handlePhotoTaken(string $path) +{ + // Process the captured photo + $this->processPhoto($path); +} +``` + +### `MediaSelected` + +Fired when media is selected from the gallery. + +**Payload:** `array $media` - Array of selected media items + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Gallery\MediaSelected; + +#[On('native:' . MediaSelected::class)] +public function handleMediaSelected($success, $files, $count) +{ + foreach ($files as $file) { + // Process each selected media item + $this->processMedia($file); + } +} +``` + +## Example Usage + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\Camera; +use Native\Mobile\Events\Camera\PhotoTaken; +use Native\Mobile\Events\Gallery\MediaSelected; + +class PhotoManager extends Component +{ + public array $photos = []; + public bool $isCapturing = false; + + public function takePhoto() + { + $this->isCapturing = true; + Camera::getPhoto(); + } + + public function pickFromGallery() + { + Camera::pickImages('images', true, 5); + } + + #[On('native:' . PhotoTaken::class)] + public function handlePhotoTaken(string $path) + { + $this->isCapturing = false; + $this->addPhoto($path); + } + + #[On('native:' . MediaSelected::class)] + public function handleMediaSelected($success, $files, $count) + { + foreach ($files as $file) { + $this->addPhoto($file); + } + } + + private function addPhoto(string $path) + { + $this->photos[] = [ + 'path' => $path, + 'data_url' => $this->createDataUrl($path), + 'timestamp' => now() + ]; + } + + private function createDataUrl(string $path): string + { + $data = base64_encode(file_get_contents($path)); + $mime = mime_content_type($path); + return "data:$mime;base64,$data"; + } + + public function render() + { + return view('livewire.photo-manager'); + } +} +``` + +## Platform Support + +- **iOS:** Uses UIImagePickerController and Photos framework +- **Android:** Uses `Intent.ACTION_IMAGE_CAPTURE` and gallery intents +- **Permissions:** Camera permission required for photo capture +- **File Location:** Photos saved to app's temporary directory + +## Notes + +- The first time your app requests camera access, users will be prompted for permission +- If permission is denied, camera functions will fail silently +- Captured photos are stored in the app's temporary directory +- Consider implementing cleanup for old temporary photos +- File formats are platform-dependent (typically JPEG) diff --git a/resources/views/docs/mobile/1/apis/dialog.md b/resources/views/docs/mobile/1/apis/dialog.md new file mode 100644 index 00000000..59b958a5 --- /dev/null +++ b/resources/views/docs/mobile/1/apis/dialog.md @@ -0,0 +1,249 @@ +--- +title: Dialog +order: 300 +--- + +## Overview + +The Dialog API provides access to native UI elements like alerts, toasts, and sharing interfaces. + +```php +use Native\Mobile\Facades\Dialog; +``` + +## Methods + +### `alert()` + +Displays a native alert dialog with customizable buttons. + +**Parameters:** +- `string $title` - The alert title +- `string $message` - The alert message +- `array $buttons` - Array of button configurations +- `callable $callback` - Callback function for button presses + +```php +Dialog::alert( + 'Confirm Action', + 'Are you sure you want to delete this item?', + [ + ['text' => 'Cancel', 'style' => 'cancel'], + ['text' => 'Delete', 'style' => 'destructive'] + ], + function($buttonIndex) { + // Handle button press + } +); +``` + +### `toast()` + +Displays a brief toast notification message. + +**Parameters:** +- `string $message` - The message to display + +```php +Dialog::toast('Item saved successfully!'); +``` + +### `share()` + +Opens the native sharing interface. + +**Parameters:** +- `string $title` - The share dialog title +- `string $text` - Text content to share +- `string $url` - URL to share + +```php +Dialog::share( + 'Check this out!', + 'I found this amazing Laravel package for mobile development', + 'https://nativephp.com' +); +``` + +## Events + +### `ButtonPressed` + +Fired when a button is pressed in an alert dialog. + +**Payload:** `int $buttonIndex` - Index of the pressed button (0-based) + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Alert\ButtonPressed; + +#[On('native:' . ButtonPressed::class)] +public function handleAlertButton(int $buttonIndex) +{ + switch ($buttonIndex) { + case 0: + // First button (usually Cancel) + break; + case 1: + // Second button (usually OK/Confirm) + $this->performAction(); + break; + } +} +``` + +## Example Usage + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\Dialog; +use Native\Mobile\Events\Alert\ButtonPressed; + +class ItemManager extends Component +{ + public array $items = []; + public ?int $itemToDelete = null; + + public function deleteItem(int $itemId) + { + $this->itemToDelete = $itemId; + + Dialog::alert( + 'Delete Item', + 'This action cannot be undone. Are you sure?', + [ + ['text' => 'Cancel', 'style' => 'cancel'], + ['text' => 'Delete', 'style' => 'destructive'] + ], + null + ); + } + + #[On('native:' . ButtonPressed::class)] + public function handleDeleteConfirmation(int $buttonIndex) + { + if ($buttonIndex === 1 && $this->itemToDelete) { + // User confirmed deletion + $this->performDelete($this->itemToDelete); + Dialog::toast('Item deleted successfully'); + $this->itemToDelete = null; + } else { + // User cancelled + $this->itemToDelete = null; + } + } + + public function shareItem(array $item) + { + Dialog::share( + 'Share Item', + "Check out this item: {$item['name']}", + "https://myapp.com/items/{$item['id']}" + ); + } + + public function showSuccess(string $message) + { + Dialog::toast($message); + } + + private function performDelete(int $itemId) + { + $this->items = array_filter( + $this->items, + fn($item) => $item['id'] !== $itemId + ); + } + + public function render() + { + return view('livewire.item-manager'); + } +} +``` + +## Alert Button Styles + +### iOS Button Styles +- `'default'` - Standard blue button +- `'cancel'` - Bold cancel button (usually on the left) +- `'destructive'` - Red destructive action button + +### Android Button Styles +- `'positive'` - Primary action button +- `'negative'` - Cancel/dismiss button +- `'neutral'` - Additional option button + +```php +// Cross-platform alert with proper styling +Dialog::alert( + 'Delete Account', + 'This will permanently delete your account and all data.', + [ + ['text' => 'Cancel', 'style' => 'cancel'], + ['text' => 'Delete', 'style' => 'destructive'] + ], + null +); +``` + +## Toast Guidelines + +### Best Practices +- Keep messages short and clear +- Use for confirmations and status updates +- Don't rely on toasts for critical information +- Avoid showing multiple toasts in quick succession + +```php +// Good toast messages +Dialog::toast('Saved!'); +Dialog::toast('Photo uploaded'); +Dialog::toast('Settings updated'); + +// Avoid long messages +Dialog::toast('Your photo has been successfully uploaded to the server and will be processed shortly'); +``` + +## Sharing Content + +### Supported Content Types +- Plain text +- URLs +- Images (when sharing files) +- Mixed content + +```php +// Share just text +Dialog::share('', 'Check out this amazing app!', ''); + +// Share a URL +Dialog::share('', '', 'https://nativephp.com'); + +// Share text and URL together +Dialog::share( + 'NativePHP for Mobile', + 'Build mobile apps with PHP and Laravel!', + 'https://nativephp.com' +); +``` + +## Platform Differences + +### iOS +- Alerts use UIAlertController +- Toasts use custom overlay views +- Sharing uses UIActivityViewController + +### Android +- Alerts use AlertDialog +- Toasts use native Toast system +- Sharing uses Intent.ACTION_SEND + +## Accessibility + +- All dialogs automatically support screen readers +- Button text should be descriptive +- Toast messages are announced by accessibility services +- Consider users with motor disabilities when designing button layouts diff --git a/resources/views/docs/mobile/1/apis/geolocation.md b/resources/views/docs/mobile/1/apis/geolocation.md new file mode 100644 index 00000000..308ac08c --- /dev/null +++ b/resources/views/docs/mobile/1/apis/geolocation.md @@ -0,0 +1,438 @@ +--- +title: Geolocation +order: 400 +--- + +## Overview + +The Geolocation API provides access to the device's GPS and location services to determine the user's current position. + +```php +use Native\Mobile\Facades\Geolocation; +``` + +## Methods + +### `getCurrentPosition()` + +Gets the current GPS location of the device. + +**Parameters:** +- `bool $fineAccuracy` - Whether to use high accuracy mode (GPS vs network) (default: `false`) + +**Returns:** Location data via events + +```php +// Get location using network positioning (faster, less accurate) +Geolocation::getCurrentPosition(); + +// Get location using GPS (slower, more accurate) +Geolocation::getCurrentPosition(true); +``` + +### `checkPermissions()` + +Checks the current location permissions status. + +**Returns:** Permission status via events + +```php +Geolocation::checkPermissions(); +``` + +### `requestPermissions()` + +Requests location permissions from the user. + +**Returns:** Permission status after request via events + +```php +Geolocation::requestPermissions(); +``` + +## Events + +### `LocationReceived` + +Fired when location data is requested (success or failure). + +**Event Parameters:** +- `bool $success` - Whether location was successfully retrieved +- `float $latitude` - Latitude coordinate (when successful) +- `float $longitude` - Longitude coordinate (when successful) +- `float $accuracy` - Accuracy in meters (when successful) +- `int $timestamp` - Unix timestamp of location fix +- `string $provider` - Location provider used (GPS, network, etc.) +- `string $error` - Error message (when unsuccessful) + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Geolocation\LocationReceived; + +#[On('native:' . LocationReceived::class)] +public function handleLocationReceived($success = null, $latitude = null, $longitude = null, $accuracy = null, $timestamp = null, $provider = null, $error = null) +{ + if ($success) { + // Location successfully retrieved + $this->latitude = $latitude; + $this->longitude = $longitude; + $this->accuracy = $accuracy; + $this->provider = $provider; + + Log::info('Location received', [ + 'lat' => $latitude, + 'lng' => $longitude, + 'accuracy' => $accuracy, + 'provider' => $provider + ]); + } else { + // Location request failed + $this->error = $error ?? 'Failed to get location'; + Log::warning('Location request failed', ['error' => $error]); + } +} +``` + +### `PermissionStatusReceived` + +Fired when permission status is checked. + +**Event Parameters:** +- `string $location` - Overall location permission status +- `string $coarseLocation` - Coarse location permission status +- `string $fineLocation` - Fine location permission status + +**Permission Values:** +- `'granted'` - Permission is granted +- `'denied'` - Permission is denied +- `'not_determined'` - Permission not yet requested + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Geolocation\PermissionStatusReceived; + +#[On('native:' . PermissionStatusReceived::class)] +public function handlePermissionStatus($location, $coarseLocation, $fineLocation) +{ + $this->locationPermission = $location; + $this->coarsePermission = $coarseLocation; + $this->finePermission = $fineLocation; + + if ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // At least some location permission is granted + $this->canRequestLocation = true; + } else { + // No location permissions granted + $this->showPermissionExplanation(); + } +} +``` + +### `PermissionRequestResult` + +Fired when a permission request completes. + +**Event Parameters:** +- `string $location` - Overall location permission result +- `string $coarseLocation` - Coarse location permission result +- `string $fineLocation` - Fine location permission result +- `string $message` - Optional message (for permanently denied) +- `bool $needsSettings` - Whether user needs to go to Settings + +**Special Values:** +- `'permanently_denied'` - User has permanently denied permission + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Geolocation\PermissionRequestResult; + +#[On('native:' . PermissionRequestResult::class)] +public function handlePermissionRequest($location, $coarseLocation, $fineLocation, $message = null, $needsSettings = null) +{ + if ($location === 'permanently_denied') { + // Permission permanently denied - must go to Settings + $this->error = $message ?? 'Location permission permanently denied. Please enable in Settings.'; + $this->showSettingsPrompt = true; + } elseif ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // Permission granted - can now request location + $this->permissionGranted = true; + $this->getCurrentLocation(); + } else { + // Permission denied but can ask again + $this->error = 'Location permission is required for this feature.'; + } +} +``` + +## Complete Example + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\Geolocation; +use Native\Mobile\Events\Geolocation\LocationReceived; +use Native\Mobile\Events\Geolocation\PermissionStatusReceived; +use Native\Mobile\Events\Geolocation\PermissionRequestResult; + +class LocationTracker extends Component +{ + public ?float $latitude = null; + public ?float $longitude = null; + public ?float $accuracy = null; + public ?string $provider = null; + public bool $isLoading = false; + public string $error = ''; + public bool $showSettingsPrompt = false; + + // Permission states + public string $locationPermission = 'unknown'; + public string $coarsePermission = 'unknown'; + public string $finePermission = 'unknown'; + + public function mount() + { + // Check current permissions on load + $this->checkPermissions(); + } + + public function checkPermissions() + { + $this->error = ''; + Geolocation::checkPermissions(); + } + + public function requestPermissions() + { + $this->error = ''; + $this->isLoading = true; + Geolocation::requestPermissions(); + } + + public function getCurrentLocation() + { + $this->isLoading = true; + $this->error = ''; + + // Use high accuracy GPS + Geolocation::getCurrentPosition(true); + } + + #[On('native:' . PermissionStatusReceived::class)] + public function handlePermissionStatus($location, $coarseLocation, $fineLocation) + { + $this->locationPermission = $location; + $this->coarsePermission = $coarseLocation; + $this->finePermission = $fineLocation; + + if ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // Has some level of location permission + $this->showLocationButton = true; + } elseif ($location === 'denied') { + // Permission denied - can request again + $this->showRequestButton = true; + } else { + // Permission not determined - can request + $this->showRequestButton = true; + } + } + + #[On('native:' . PermissionRequestResult::class)] + public function handlePermissionRequest($location, $coarseLocation, $fineLocation, $message = null, $needsSettings = null) + { + $this->isLoading = false; + + if ($location === 'permanently_denied') { + $this->error = $message ?? 'Location access permanently denied. Please enable location services in your device Settings app.'; + $this->showSettingsPrompt = true; + } elseif ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // Permission granted - automatically get location + $this->getCurrentLocation(); + } else { + $this->error = 'Location permission is required to use this feature.'; + } + } + + #[On('native:' . LocationReceived::class)] + public function handleLocationReceived($success = null, $latitude = null, $longitude = null, $accuracy = null, $timestamp = null, $provider = null, $error = null) + { + $this->isLoading = false; + + if ($success) { + $this->latitude = $latitude; + $this->longitude = $longitude; + $this->accuracy = $accuracy; + $this->provider = $provider; + $this->error = ''; + + // Store for later use + session([ + 'last_location' => [ + 'lat' => $latitude, + 'lng' => $longitude, + 'accuracy' => $accuracy, + 'timestamp' => $timestamp, + 'provider' => $provider + ] + ]); + + Log::info('Location updated', [ + 'lat' => $latitude, + 'lng' => $longitude, + 'accuracy' => $accuracy + ]); + + } else { + $this->error = $error ?? 'Failed to get current location'; + Log::warning('Location request failed', ['error' => $error]); + } + } + + public function openSettings() + { + // You might want to show instructions or deep link to settings + $this->dispatch('show-settings-instructions'); + } + + public function render() + { + return view('livewire.location-tracker'); + } +} +``` + +## Understanding Permission States + +### Permission Types + +**Coarse Location (`ACCESS_COARSE_LOCATION` on Android)** +- Network-based location (WiFi, cellular towers) +- Lower accuracy (~100-1000 meters) +- Less battery usage +- Faster location fixes + +**Fine Location (`ACCESS_FINE_LOCATION` on Android, Location Services on iOS)** +- GPS-based location +- Higher accuracy (~5-50 meters) +- More battery usage +- Slower initial location fix + +### Permission Flow + +```php +class PermissionFlowExample extends Component +{ + public function handleLocationFlow() + { + // 1. Check current permissions + Geolocation::checkPermissions(); + } + + #[On('native:' . PermissionStatusReceived::class)] + public function handleCheck($location, $coarseLocation, $fineLocation) + { + if ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // 2a. Permission already granted - get location + Geolocation::getCurrentPosition(true); + } else { + // 2b. Need to request permission + Geolocation::requestPermissions(); + } + } + + #[On('native:' . PermissionRequestResult::class)] + public function handleRequest($location, $coarseLocation, $fineLocation, $message = null, $needsSettings = null) + { + if ($location === 'permanently_denied') { + // 3a. User must enable in Settings + $this->showSettingsInstructions($message); + } elseif ($coarseLocation === 'granted' || $fineLocation === 'granted') { + // 3b. Permission granted - get location + Geolocation::getCurrentPosition(true); + } else { + // 3c. Permission denied - show explanation + $this->showPermissionExplanation(); + } + } +} +``` + +## Platform Support + +### iOS +- Uses Core Location framework +- Requires location usage description in Info.plist +- Supports both "When in Use" and "Always" permissions +- Automatic permission prompts + +### Android +- Uses FusedLocationProviderClient (Google Play Services) +- Requires location permissions in AndroidManifest.xml +- Supports coarse and fine location permissions +- Runtime permission requests (Android 6+) + +## Privacy Considerations + +### Best Practices +- **Explain why** you need location access before requesting +- **Request at the right time** - when the feature is actually needed +- **Respect denials** - provide alternative functionality when possible +- **Use appropriate accuracy** - don't request fine location if coarse is sufficient +- **Limit frequency** - don't request location updates constantly + +### User Experience Tips + +```php +class LocationUX extends Component +{ + public function requestLocationWithExplanation() + { + // Show explanation first + $this->showExplanation = true; + } + + public function proceedWithLocationRequest() + { + $this->showExplanation = false; + + // Now request permission + Geolocation::requestPermissions(); + } + + public function handleDeniedGracefully($location, $coarseLocation, $fineLocation) + { + if ($location === 'permanently_denied') { + // Offer manual location entry + $this->showManualLocationEntry = true; + } else { + // Show benefit of enabling location + $this->showLocationBenefits = true; + } + } +} +``` + +## Accuracy and Performance + +### Choosing Accuracy Level + +```php +// For general location (city-level) +Geolocation::getCurrentPosition(false); // ~100-1000m accuracy + +// For precise location (navigation, delivery) +Geolocation::getCurrentPosition(true); // ~5-50m accuracy +``` + +### Performance Considerations +- **Battery Usage** - GPS uses more battery than network location +- **Time to Fix** - GPS takes longer for initial position +- **Indoor Accuracy** - GPS may not work well indoors +- **Caching** - Consider caching recent locations for better UX + +### Error Handling +- Always handle both success and failure cases +- Provide meaningful error messages to users +- Implement fallback strategies (manual entry, saved locations) +- Log errors for debugging but don't expose sensitive details + +The Geolocation API provides powerful location capabilities while respecting user privacy and platform requirements. Always handle permissions gracefully and provide clear value propositions for why location access is needed. diff --git a/resources/views/docs/mobile/1/apis/haptics.md b/resources/views/docs/mobile/1/apis/haptics.md new file mode 100644 index 00000000..6cc1e290 --- /dev/null +++ b/resources/views/docs/mobile/1/apis/haptics.md @@ -0,0 +1,80 @@ +--- +title: Haptics +order: 500 +--- + +## Overview + +The Haptics API provides access to the device's vibration and haptic feedback system for tactile user interactions. + +```php +use Native\Mobile\Facades\Haptics; +``` + +## Methods + +### `vibrate()` + +Triggers device vibration for tactile feedback. + +**Returns:** `void` + +```php +Haptics::vibrate(); +``` + +## Example Usage + +### Basic Form Feedback +```php +use Livewire\Component; +use Native\Mobile\Facades\Haptics; + +class FormComponent extends Component +{ + public function save() + { + if ($this->hasErrors()) { + // Haptic feedback for errors + Haptics::vibrate(); + return; + } + + $this->saveData(); + + // Success haptic feedback + Haptics::vibrate(); + } + + public function deleteItem() + { + // Haptic feedback for important actions + Haptics::vibrate(); + $this->performDelete(); + } +} +``` + +### Best Practices +```php +class HapticsExample extends Component +{ + // βœ… Good: Button presses, form errors, important actions + public function onButtonPress() + { + Haptics::vibrate(); + $this->processAction(); + } + + // ❌ Avoid: Frequent events like scrolling + public function onScroll() + { + // Don't vibrate on every scroll - too annoying! + // Haptics::vibrate(); + } +} +``` + +**Use haptics for:** Button presses, form validation, important notifications, game events +**Avoid haptics for:** Frequent events, background processes, minor updates + diff --git a/resources/views/docs/mobile/1/apis/overview.md b/resources/views/docs/mobile/1/apis/overview.md new file mode 100644 index 00000000..234fdbdb --- /dev/null +++ b/resources/views/docs/mobile/1/apis/overview.md @@ -0,0 +1,130 @@ +--- +title: Overview +order: 1 +--- +# API Reference + +Complete documentation for all NativePHP Mobile APIs. Each API provides access to native device capabilities through familiar PHP facades. + +## Available APIs + +### Biometrics +**Face ID, Touch ID, Fingerprint Authentication** +```php +Biometrics::promptForBiometricID(); +``` +Secure user authentication using device biometric sensors. Supports Face ID on iOS, Touch ID, and fingerprint readers on Android. + +### Camera +**Photo Capture & Gallery Access** +```php +Camera::getPhoto(); +Camera::pickImages('images', true, 5); +``` +Take photos with the device camera or select images from the photo gallery. Supports both single and multiple image selection. + +### Dialog +**Native UI Elements** +```php +Dialog::alert('Title', 'Message', $buttons, $callback); +Dialog::toast('Success message'); +Dialog::share('Title', 'Text', 'https://example.com'); +``` +Display native alerts, toast notifications, and sharing interfaces that match platform design guidelines. + +### Geolocation +**GPS & Location Services** +```php +Geolocation::getCurrentPosition(true); // High accuracy +Geolocation::checkPermissions(); +Geolocation::requestPermissions(); +``` +Access device location services with configurable accuracy levels and proper permission handling. + +### Haptics +**Vibration & Tactile Feedback** +```php +Haptics::vibrate(); +``` +Provide tactile feedback for user interactions, form validation, and important events. + +### PushNotifications +**Firebase Cloud Messaging** +```php +PushNotifications::enrollForPushNotifications(); +PushNotifications::getPushNotificationsToken(); +``` +Register devices for push notifications and manage FCM tokens for server-side notification delivery. + +### SecureStorage +**Keychain & Keystore Operations** +```php +SecureStorage::set('api_token', $token); +$token = SecureStorage::get('api_token'); +SecureStorage::delete('api_token'); +``` +Store sensitive data securely using iOS Keychain and Android Keystore with automatic encryption. + +### System +**System Functions & Legacy API** +```php +System::flashlight(); // Toggle flashlight +``` +Control system functions like the flashlight. Also provides deprecated methods that have moved to dedicated facades. + +## API Patterns + +### Synchronous APIs +Execute immediately and return results: +- `Haptics::vibrate()` +- `System::flashlight()` +- `Dialog::toast()` +- `SecureStorage::set()` / `get()` + +### Asynchronous APIs +Trigger operations and fire events when complete: +- `Camera::getPhoto()` β†’ `PhotoTaken` event +- `Camera::pickImages()` β†’ `MediaSelected` event +- `Biometrics::promptForBiometricID()` β†’ `Completed` event +- `PushNotifications::enrollForPushNotifications()` β†’ `TokenGenerated` event +- `Geolocation::getCurrentPosition()` β†’ `LocationReceived` event +- `Geolocation::checkPermissions()` β†’ `PermissionStatusReceived` event +- `Geolocation::requestPermissions()` β†’ `PermissionRequestResult` event +- `Dialog::alert()` β†’ `ButtonPressed` event + +### Event Handling +All async APIs use Laravel events with Livewire integration: + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\Camera\PhotoTaken; + +#[On('native:' . PhotoTaken::class)] +public function handlePhotoTaken(string $path) +{ + // Process the captured photo +} +``` + + +## Platform Support + +All APIs work on both iOS and Android with platform-appropriate implementations: +- **iOS**: Uses native iOS frameworks and APIs +- **Android**: Uses Android SDK and native libraries +- **Permissions**: Automatically handled with user prompts when required +- **Fallbacks**: Graceful degradation when features aren't available + +## Error Handling + +APIs provide both success and error events for proper error handling: + +```php +#[On('native:' . PhotoTaken::class)] +public function handleSuccess($data) { /* ... */ } + +#[On('native:' . PermissionDenied::class)] +public function handleError($error) { /* ... */ } +``` + +Each API documentation includes complete error handling examples and best practices. diff --git a/resources/views/docs/mobile/1/apis/push-notifications.md b/resources/views/docs/mobile/1/apis/push-notifications.md new file mode 100644 index 00000000..504e7124 --- /dev/null +++ b/resources/views/docs/mobile/1/apis/push-notifications.md @@ -0,0 +1,398 @@ +--- +title: PushNotifications +order: 600 +--- + +## Overview + +The PushNotifications API handles device registration for Firebase Cloud Messaging to receive push notifications. + +```php +use Native\Mobile\Facades\PushNotifications; +``` + +## Methods + +### `enrollForPushNotifications()` + +Requests permission and enrolls the device for push notifications. + +**Returns:** `void` + +```php +PushNotifications::enrollForPushNotifications(); +``` + +### `getPushNotificationsToken()` + +Retrieves the current FCM token for this device. + +**Returns:** `string|null` - The FCM token, or `null` if not available + +```php +$token = PushNotifications::getPushNotificationsToken(); + +if ($token) { + // Send token to your server + $this->registerTokenWithServer($token); +} else { + // Token not available, enrollment may have failed +} +``` + +## Events + +### `TokenGenerated` + +Fired when a push notification token is successfully generated. + +**Payload:** `string $token` - The FCM token for this device + +```php +use Livewire\Attributes\On; +use Native\Mobile\Events\PushNotification\TokenGenerated; + +#[On('native:' . TokenGenerated::class)] +public function handlePushToken(string $token) +{ + // Send token to your backend + $this->sendTokenToServer($token); +} +``` + +## Example Usage + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\PushNotifications; +use Native\Mobile\Events\PushNotification\TokenGenerated; + +class NotificationManager extends Component +{ + public bool $isRegistered = false; + public bool $isRegistering = false; + public string $error = ''; + + public function mount() + { + // Check if already registered + $this->checkExistingRegistration(); + } + + public function enableNotifications() + { + $this->isRegistering = true; + $this->error = ''; + + // Request permission and get token + PushNotifications::enrollForPushNotifications(); + } + + #[On('native:' . TokenGenerated::class)] + public function handleTokenGenerated(string $token) + { + $this->isRegistering = false; + + try { + // Send token to your backend API + $response = Http::withToken(session('api_token')) + ->post('/api/push-tokens', [ + 'token' => $token, + 'device_id' => $this->getDeviceId(), + 'platform' => $this->getPlatform(), + 'user_id' => auth()->id() + ]); + + if ($response->successful()) { + $this->isRegistered = true; + session(['push_token' => $token]); + + Log::info('Push notification token registered', [ + 'user_id' => auth()->id(), + 'token_preview' => substr($token, 0, 10) . '...' + ]); + } else { + throw new Exception('Server rejected token registration'); + } + + } catch (Exception $e) { + $this->error = 'Failed to register for notifications: ' . $e->getMessage(); + + Log::error('Push token registration failed', [ + 'error' => $e->getMessage(), + 'user_id' => auth()->id() + ]); + } + } + + public function disableNotifications() + { + $token = session('push_token'); + + if ($token) { + try { + // Remove token from server + Http::withToken(session('api_token')) + ->delete("/api/push-tokens/{$token}"); + + session()->forget('push_token'); + $this->isRegistered = false; + + } catch (Exception $e) { + $this->error = 'Failed to disable notifications'; + } + } + } + + private function checkExistingRegistration() + { + $existingToken = session('push_token'); + + if ($existingToken) { + // Verify token is still valid + $currentToken = PushNotifications::getPushNotificationsToken(); + + if ($currentToken === $existingToken) { + $this->isRegistered = true; + } else { + // Token changed, need to re-register + session()->forget('push_token'); + $this->isRegistered = false; + } + } + } + + private function getDeviceId(): string + { + if (!session()->has('device_id')) { + session(['device_id' => Str::uuid()]); + } + + return session('device_id'); + } + + private function getPlatform(): string + { + // Detect platform from user agent or environment + return request()->header('X-Platform', 'unknown'); + } + + public function render() + { + return view('livewire.notification-manager'); + } +} +``` + +## Backend Integration + +### Database Schema + +```php +// Migration for storing push tokens +Schema::create('push_tokens', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('token')->unique(); + $table->string('device_id')->nullable(); + $table->enum('platform', ['ios', 'android', 'unknown']); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'platform']); +}); +``` + +### API Controller + +```php +namespace App\Http\Controllers\Api; + +use App\Models\PushToken; +use Illuminate\Http\Request; + +class PushTokenController extends Controller +{ + public function store(Request $request) + { + $validated = $request->validate([ + 'token' => 'required|string|max:255', + 'device_id' => 'nullable|string|max:255', + 'platform' => 'required|in:ios,android,unknown' + ]); + + PushToken::updateOrCreate( + [ + 'user_id' => $request->user()->id, + 'device_id' => $validated['device_id'] + ], + [ + 'token' => $validated['token'], + 'platform' => $validated['platform'], + 'last_used_at' => now() + ] + ); + + return response()->json(['message' => 'Token registered successfully']); + } + + public function destroy(Request $request, string $token) + { + PushToken::where('user_id', $request->user()->id) + ->where('token', $token) + ->delete(); + + return response()->json(['message' => 'Token removed successfully']); + } +} +``` + +### Sending Notifications + +```php +namespace App\Services; + +use Google\Client as GoogleClient; +use Google\Service\FirebaseCloudMessaging; + +class PushNotificationService +{ + public function sendToUser(int $userId, array $notification, array $data = []) + { + $tokens = PushToken::where('user_id', $userId) + ->pluck('token') + ->toArray(); + + if (empty($tokens)) { + throw new Exception('No push tokens found for user'); + } + + return $this->sendToTokens($tokens, $notification, $data); + } + + private function sendToTokens(array $tokens, array $notification, array $data = []) + { + $client = new GoogleClient(); + $client->setAuthConfig(base_path('google-services.json')); + $client->addScope('https://www.googleapis.com/auth/firebase.messaging'); + + $fcm = new FirebaseCloudMessaging($client); + $projectId = config('services.firebase.project_id'); + + $results = []; + + foreach ($tokens as $token) { + try { + $message = [ + 'token' => $token, + 'notification' => $notification, + 'data' => array_map('strval', $data) + ]; + + $response = $fcm->projects_messages->send($projectId, [ + 'message' => $message + ]); + + $results[] = [ + 'token' => substr($token, 0, 10) . '...', + 'success' => true, + 'message_id' => $response->getName() + ]; + + } catch (Exception $e) { + $results[] = [ + 'token' => substr($token, 0, 10) . '...', + 'success' => false, + 'error' => $e->getMessage() + ]; + + // Remove invalid tokens + if (str_contains($e->getMessage(), 'registration-token-not-registered')) { + PushToken::where('token', $token)->delete(); + } + } + } + + return $results; + } +} +``` + +## Configuration Requirements + +### Firebase Setup + +1. Create a Firebase project at [Firebase Console](https://console.firebase.google.com/) +2. Add your mobile app to the project +3. Download `google-services.json` (Android) and `GoogleService-Info.plist` (iOS) +4. Place these files in your Laravel project root +5. Enable push notifications in your NativePHP config: + +```php +// config/nativephp.php +return [ + 'permissions' => [ + 'push_notifications' => true, + ], +]; +``` + +### Environment Variables + +```bash +NATIVEPHP_APP_ID=com.yourcompany.yourapp +FIREBASE_PROJECT_ID=your-firebase-project-id +``` + +## Permission Flow + +1. User taps "Enable Notifications" +2. App calls `enrollForPushNotifications()` +3. System shows permission dialog +4. If granted, FCM generates token +5. `TokenGenerated` event fires with token +6. App sends token to backend +7. Backend stores token for user +8. Server can now send notifications to this device + +## Error Handling + +```php +public function handleRegistrationFailure() +{ + // Common failure scenarios: + + // 1. User denied permission + if (!$this->hasNotificationPermission()) { + $this->showPermissionExplanation(); + return; + } + + // 2. Network error + if (!$this->hasNetworkConnection()) { + $this->showNetworkError(); + return; + } + + // 3. Firebase configuration missing + if (!$this->hasFirebaseConfig()) { + Log::error('Firebase configuration missing'); + return; + } + + // 4. Backend API error + $this->showGenericError(); +} +``` + + +## Best Practices + +- Request permission at the right time (not immediately on app launch) +- Explain the value of notifications to users +- Handle permission denial gracefully +- Clean up invalid tokens on your backend +- Implement retry logic for network failures +- Log registration events for debugging +- Respect user preferences and provide opt-out diff --git a/resources/views/docs/mobile/1/apis/secure-storage.md b/resources/views/docs/mobile/1/apis/secure-storage.md new file mode 100644 index 00000000..497fbdc5 --- /dev/null +++ b/resources/views/docs/mobile/1/apis/secure-storage.md @@ -0,0 +1,275 @@ +--- +title: SecureStorage +order: 700 +--- + +## Overview + +The SecureStorage API provides secure storage using the device's native keychain (iOS) or keystore (Android) for sensitive data like tokens, passwords, and user credentials. + +```php +use Native\Mobile\Facades\SecureStorage; +``` + +## Methods + +### `set()` + +Stores a secure value in the native keychain or keystore. + +**Parameters:** +- `string $key` - The key to store the value under +- `string|null $value` - The value to store securely + +**Returns:** `bool` - `true` if successfully stored, `false` otherwise + +```php +$success = SecureStorage::set('api_token', 'abc123xyz'); + +if ($success) { + // Token stored securely +} else { + // Storage failed +} +``` + +### `get()` + +Retrieves a secure value from the native keychain or keystore. + +**Parameters:** +- `string $key` - The key to retrieve the value for + +**Returns:** `string|null` - The stored value or `null` if not found + +```php +$token = SecureStorage::get('api_token'); + +if ($token) { + // Use the retrieved token + $this->authenticateWithToken($token); +} else { + // Token not found, user needs to login + $this->redirectToLogin(); +} +``` + +### `delete()` + +Deletes a secure value from the native keychain or keystore. + +**Parameters:** +- `string $key` - The key to delete the value for + +**Returns:** `bool` - `true` if successfully deleted, `false` otherwise + +```php +$deleted = SecureStorage::delete('api_token'); + +if ($deleted) { + // Token removed successfully +} else { + // Deletion failed or key didn't exist +} +``` + +## Example Usage + +```php +use Livewire\Component; +use Native\Mobile\Facades\SecureStorage; + +class AuthManager extends Component +{ + public bool $isLoggedIn = false; + public string $error = ''; + + public function mount() + { + // Check if user has stored credentials + $this->checkStoredAuth(); + } + + public function login(string $username, string $password) + { + try { + // Authenticate with your API + $response = Http::post('/api/login', [ + 'username' => $username, + 'password' => $password + ]); + + if ($response->successful()) { + $data = $response->json(); + + // Store tokens securely + SecureStorage::set('access_token', $data['access_token']); + SecureStorage::set('refresh_token', $data['refresh_token']); + SecureStorage::set('user_id', (string) $data['user']['id']); + + $this->isLoggedIn = true; + } else { + $this->error = 'Login failed'; + } + } catch (Exception $e) { + $this->error = 'Network error: ' . $e->getMessage(); + } + } + + public function logout() + { + // Clear stored credentials + SecureStorage::delete('access_token'); + SecureStorage::delete('refresh_token'); + SecureStorage::delete('user_id'); + + $this->isLoggedIn = false; + } + + private function checkStoredAuth() + { + $accessToken = SecureStorage::get('access_token'); + + if ($accessToken) { + // Verify token is still valid + $response = Http::withToken($accessToken) + ->get('/api/user'); + + if ($response->successful()) { + $this->isLoggedIn = true; + } else { + // Token expired, try refresh + $this->refreshToken(); + } + } + } + + private function refreshToken() + { + $refreshToken = SecureStorage::get('refresh_token'); + + if ($refreshToken) { + $response = Http::post('/api/refresh', [ + 'refresh_token' => $refreshToken + ]); + + if ($response->successful()) { + $data = $response->json(); + SecureStorage::set('access_token', $data['access_token']); + $this->isLoggedIn = true; + } else { + // Refresh failed, clear everything + $this->logout(); + } + } + } + + public function render() + { + return view('livewire.auth-manager'); + } +} +``` + +## Platform Implementation + +### iOS - Keychain Services +- Uses the iOS Keychain Services API +- Data is encrypted and tied to your app's bundle ID +- Survives app deletion and reinstallation if iCloud Keychain is enabled +- Protected by device passcode/biometrics + +### Android - Keystore +- Uses Android Keystore system +- Hardware-backed encryption when available +- Data is automatically deleted when app is uninstalled +- Protected by device lock screen + +## Security Features + +- **Encryption:** All data is automatically encrypted +- **App Isolation:** Data is only accessible by your app +- **System Protection:** Protected by device authentication +- **Tamper Resistance:** Hardware-backed security when available + +## Best Practices + +### What to Store +- API tokens and refresh tokens +- User credentials (if necessary) +- Encryption keys +- Sensitive user preferences +- Two-factor authentication secrets + +### What NOT to Store +- Large amounts of data (use encrypted database instead) +- Non-sensitive configuration +- Temporary data +- Cached content + +### Implementation Tips + +```php +class SecureSettings +{ + public function storeUserCredentials(string $userId, string $token) + { + // Use prefixed keys for organization + SecureStorage::set("user_{$userId}_token", $token); + SecureStorage::set("user_{$userId}_last_login", now()->toISOString()); + } + + public function getUserToken(string $userId): ?string + { + return SecureStorage::get("user_{$userId}_token"); + } + + public function clearUserData(string $userId) + { + // Clean up all user-related secure data + SecureStorage::delete("user_{$userId}_token"); + SecureStorage::delete("user_{$userId}_last_login"); + SecureStorage::delete("user_{$userId}_preferences"); + } + + public function rotateToken(string $userId, string $newToken) + { + // Atomic token rotation + $oldToken = $this->getUserToken($userId); + + if (SecureStorage::set("user_{$userId}_token", $newToken)) { + // New token stored successfully + Log::info("Token rotated for user {$userId}"); + } else { + // Rotation failed, keep old token + Log::error("Token rotation failed for user {$userId}"); + } + } +} +``` + +## Error Handling + +```php +public function storeSecurely(string $key, string $value) +{ + $attempts = 0; + $maxAttempts = 3; + + while ($attempts < $maxAttempts) { + if (SecureStorage::set($key, $value)) { + return true; + } + + $attempts++; + usleep(100000); // Wait 100ms before retry + } + + Log::error("Failed to store secure value after {$maxAttempts} attempts", [ + 'key' => $key + ]); + + return false; +} +``` + diff --git a/resources/views/docs/mobile/1/apis/system.md b/resources/views/docs/mobile/1/apis/system.md new file mode 100644 index 00000000..aec383fd --- /dev/null +++ b/resources/views/docs/mobile/1/apis/system.md @@ -0,0 +1,55 @@ +--- +title: System +order: 800 +--- + +## Overview + +The System API provides access to basic system functions like flashlight control. + +```php +use Native\Mobile\Facades\System; +``` + +## Methods + +### `flashlight()` + +Toggles the device flashlight (camera flash LED) on and off. + +**Returns:** `void` + +```php +System::flashlight(); // Toggle flashlight state +``` + +## Example Usage + +```php +use Livewire\Component; +use Native\Mobile\Facades\System; + +class FlashlightController extends Component +{ + public bool $isFlashlightOn = false; + + public function toggleFlashlight() + { + System::flashlight(); + $this->isFlashlightOn = !$this->isFlashlightOn; + } + + public function render() + { + return view('livewire.flashlight-controller'); + } +} +``` + +## Platform Support + +### Flashlight +- **iOS:** Controls camera flash LED +- **Android:** Controls camera flash LED +- **Permissions:** None required +- **Limitations:** May not work if camera is currently in use \ No newline at end of file diff --git a/resources/views/docs/mobile/1/concepts/_index.md b/resources/views/docs/mobile/1/concepts/_index.md new file mode 100644 index 00000000..97e3b3a0 --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/_index.md @@ -0,0 +1,4 @@ +--- +title: Concepts +order: 3 +--- diff --git a/resources/views/docs/mobile/1/concepts/ci-cd.md b/resources/views/docs/mobile/1/concepts/ci-cd.md new file mode 100644 index 00000000..e2279813 --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/ci-cd.md @@ -0,0 +1,167 @@ +--- +title: CI/CD Integration +order: 500 +--- + +## Overview + +NativePHP Mobile provides robust CLI commands designed for automated CI/CD environments. With proper configuration, you can build, package, and deploy mobile apps without manual intervention. + +## Key Commands for CI/CD + +### Installation Command + +Install NativePHP dependencies in automated environments: + +```bash +# Install Android platform, overwriting existing files +php artisan native:install android --force --no-tty + +# Install with ICU support for Filament/intl features +php artisan native:install android --force --with-icu + +# Install both platforms +php artisan native:install both --force +``` + +### Build Commands + +Build your app for different environments: + +```bash +# Build debug version (development) +php artisan native:run android --build=debug --no-tty + +# Build release version (production) +php artisan native:run android --build=release --no-tty + +# Build app bundle for Play Store +php artisan native:run android --build=bundle --no-tty +``` + +### Packaging Command + +Package signed releases for distribution: + +```bash +# Package signed APK using environment variables +php artisan native:package android --build-type=release --output=/artifacts --no-tty + +# Package signed App Bundle for Play Store +php artisan native:package android --build-type=bundle --output=/artifacts --no-tty +``` + +## Environment Variables + +Store sensitive signing information in environment variables: + +```bash +# Android Signing +ANDROID_KEYSTORE_FILE="/path/to/keystore.jks" +ANDROID_KEYSTORE_PASSWORD="your-keystore-password" +ANDROID_KEY_ALIAS="your-key-alias" +ANDROID_KEY_PASSWORD="your-key-password" + +# Push Notifications (optional) +FCM_SERVER_KEY="your-fcm-server-key" +GOOGLE_SERVICE_ACCOUNT_KEY="/path/to/service-account.json" + +# App Configuration +NATIVEPHP_APP_ID="com.yourcompany.yourapp" +NATIVEPHP_APP_VERSION="1.0.0" +NATIVEPHP_APP_VERSION_CODE="1" +``` + +## Command Line Options + +### `--no-tty` Flag +Essential for CI/CD environments where TTY is not available: +- Disables interactive prompts +- Provides non-interactive output +- Shows build progress without real-time updates +- Required for most automated environments + +### `--force` Flag +Overwrites existing files and directories: +- Useful for clean builds in CI +- Ensures fresh installation of NativePHP scaffolding +- Prevents build failures from existing files + +### Build Types +- `--build=debug`: Development builds with debugging enabled +- `--build=release`: Production builds optimized for distribution +- `--build=bundle`: App bundles for Play Store distribution + +## Signing Configuration + +### Using Command Line Options +```bash +php artisan native:package android \ + --build-type=release \ + --keystore=/path/to/keystore.jks \ + --keystore-password=your-password \ + --key-alias=your-alias \ + --key-password=your-key-password \ + --output=./artifacts \ + --no-tty +``` + +### Using Environment Variables (Recommended) +```bash +# Set environment variables in CI +export ANDROID_KEYSTORE_FILE="/path/to/keystore.jks" +export ANDROID_KEYSTORE_PASSWORD="your-password" +export ANDROID_KEY_ALIAS="your-alias" +export ANDROID_KEY_PASSWORD="your-key-password" + +# Run packaging command +php artisan native:package android --build-type=release --output=./artifacts --no-tty +``` + +## Common CI/CD Workflows + +### Development Pipeline +1. Install dependencies: `composer install` +2. Setup environment: copy `.env`, generate key +3. Install NativePHP: `native:install android --force` +4. Build debug: `native:run android --build=debug --no-tty` + +### Release Pipeline +1. Install dependencies: `composer install --no-dev --optimize-autoloader` +2. Setup environment with production settings +3. Install NativePHP: `native:install android --force --with-icu` +4. Package release: `native:package android --build-type=release --no-tty` + +### Play Store Pipeline +1. Same as release pipeline through step 3 +2. Package bundle: `native:package android --build-type=bundle --no-tty` +3. Upload to Play Console via API + +## Error Handling + +NativePHP commands provide proper exit codes for CI/CD: +- `0`: Success +- `1`: General error +- Build errors are logged and reported + +Monitor build logs for: +- Compilation errors +- Signing failures +- Missing dependencies +- Permission issues + +## Performance Tips + +### Caching +Cache these directories in CI for faster builds: +- `vendor/` (Composer dependencies) +- `nativephp/android/` (Android project) +- Android SDK components + + +### Optimization +- Use `--no-dev` for production Composer installs +- Enable Composer autoloader optimization +- Minimize included files with cleanup configuration + +The `--no-tty` flag and environment variable support make NativePHP Mobile well-suited for modern CI/CD pipelines, enabling fully automated mobile app builds and deployments. diff --git a/resources/views/docs/mobile/1/concepts/databases.md b/resources/views/docs/mobile/1/concepts/databases.md new file mode 100644 index 00000000..0d485d51 --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/databases.md @@ -0,0 +1,272 @@ +--- +title: Databases +order: 200 +--- + +## Working with Databases + +You'll almost certainly want your application to persist structured data. For this, NativePHP supports +[SQLite](https://sqlite.org/), which works on both iOS and Android devices. + +You can interact with SQLite from PHP in whichever way you're used to. + +## Configuration + +You do not need to do anything special to configure your application to use SQLite. NativePHP will automatically: +- Switch to using SQLite when building your application. +- Create the database for you in the app container. +- Run your migrations each time your app starts, as needed. + +## Migrations + +When writing migrations, you need to consider any special recommendations for working with SQLite. + +For example, prior to Laravel 11, SQLite foreign key constraints are turned off by default. If your application relies +upon foreign key constraints, [you need to enable SQLite support for them](https://laravel.com/docs/database#configuration) before running your migrations. + +**It's important to test your migrations on prod builds before releasing updates!** You don't want to accidentally +delete your user's data when they update your app. + +## Things to note + +- As your app is installed on a separate device, you do not have remote access to the database. +- If a user deletes your application from their device, any databases are also deleted. + +## Database Security & Remote Data + +### Why No MySQL/PostgreSQL Support? + +NativePHP for Mobile intentionally does not include MySQL, PostgreSQL, or other remote database drivers. This is a deliberate security decision to prevent developers from accidentally embedding production database credentials directly in mobile applications. + +**Key security concerns:** +- Mobile apps are distributed to user devices and can be reverse-engineered +- Database credentials embedded in apps are accessible to anyone with the app binary +- Direct database connections bypass important security layers like rate limiting and access controls +- Network connectivity issues make direct database connections unreliable on mobile + +### The API-First Approach + +Instead of direct database connections, we strongly recommend building a secure API backend that your mobile app communicates with. This provides multiple security and architectural benefits: + +**Security Benefits:** +- Database credentials never leave your server +- Implement proper authentication and authorization +- Rate limiting and request validation +- Audit logs for all data access +- Ability to revoke access instantly + +**Technical Benefits:** +- Better error handling and offline support +- Easier to scale and maintain +- Version your API for backward compatibility +- Transform data specifically for mobile consumption + +### Recommended Architecture + +```php +// ❌ NEVER DO THIS - Direct database in mobile app +DB::connection('production')->table('users')->get(); // Credentials exposed! + +// βœ… DO THIS - Secure API communication +$response = Http::withToken($this->getStoredToken()) + ->get('https://your-api.com/api/users'); +``` + +### Laravel Sanctum Integration + +[Laravel Sanctum](https://laravel.com/docs/sanctum) is the perfect solution for API authentication between your mobile app and Laravel backend. It provides secure, token-based authentication without the complexity of OAuth. + +**Backend Setup:** +```php +// Install Sanctum in your API backend +composer require laravel/sanctum + +// Create login endpoint +Route::post('/login', function (Request $request) { + $credentials = $request->validate([ + 'email' => 'required|email', + 'password' => 'required' + ]); + + if (Auth::attempt($credentials)) { + $user = Auth::user(); + $token = $user->createToken('mobile-app')->plainTextToken; + + return response()->json([ + 'token' => $token, + 'user' => $user + ]); + } + + return response()->json(['error' => 'Invalid credentials'], 401); +}); + +// Protected API routes +Route::middleware('auth:sanctum')->group(function () { + Route::get('/user', function (Request $request) { + return $request->user(); + }); + + Route::get('/data', function (Request $request) { + // Your protected data endpoints + return Data::where('user_id', $request->user()->id)->get(); + }); +}); +``` + +### Secure Token Storage + +Use the [SecureStorage API](/docs/mobile/1/apis/secure-storage) to securely store authentication tokens on the device: + +```php +use Native\Mobile\Facades\SecureStorage; +use Illuminate\Support\Facades\Http; + +class ApiAuthManager extends Component +{ + public bool $isAuthenticated = false; + public string $error = ''; + + public function mount() + { + $this->checkStoredAuthentication(); + } + + public function login(string $email, string $password) + { + try { + $response = Http::post('https://your-api.com/api/login', [ + 'email' => $email, + 'password' => $password + ]); + + if ($response->successful()) { + $data = $response->json(); + + // Store token securely in device keychain/keystore + SecureStorage::set('api_token', $data['token']); + SecureStorage::set('user_data', json_encode($data['user'])); + + $this->isAuthenticated = true; + $this->error = ''; + } else { + $this->error = 'Invalid credentials'; + } + } catch (Exception $e) { + $this->error = 'Network error: ' . $e->getMessage(); + } + } + + public function logout() + { + // Revoke token on server + $token = SecureStorage::get('api_token'); + if ($token) { + Http::withToken($token)->post('https://your-api.com/api/logout'); + } + + // Clear local storage + SecureStorage::delete('api_token'); + SecureStorage::delete('user_data'); + + $this->isAuthenticated = false; + } + + private function checkStoredAuthentication() + { + $token = SecureStorage::get('api_token'); + + if ($token) { + // Verify token is still valid + $response = Http::withToken($token) + ->get('https://your-api.com/api/user'); + + if ($response->successful()) { + $this->isAuthenticated = true; + } else { + // Token expired or invalid + SecureStorage::delete('api_token'); + SecureStorage::delete('user_data'); + } + } + } + + public function makeAuthenticatedRequest(string $endpoint) + { + $token = SecureStorage::get('api_token'); + + if (!$token) { + throw new Exception('No authentication token available'); + } + + $response = Http::withToken($token) + ->get("https://your-api.com/api/{$endpoint}"); + + if ($response->status() === 401) { + // Token expired + $this->logout(); + throw new Exception('Authentication expired'); + } + + return $response->json(); + } +} +``` + +### Best Practices + +**For Mobile Apps:** +- Always store API tokens in SecureStorage +- Implement proper error handling for network requests +- Cache data locally using SQLite for offline functionality +- Use HTTPS for all API communications +- Implement retry logic with exponential backoff + +**For API Backends:** +- Use Laravel Sanctum or similar for token-based authentication +- Implement rate limiting to prevent abuse +- Validate and sanitize all input data +- Use HTTPS with proper SSL certificates +- Log all authentication attempts and API access + +**Token Management:** +```php +class TokenManager +{ + public function refreshTokenIfNeeded(): bool + { + $token = SecureStorage::get('api_token'); + $tokenExpiry = SecureStorage::get('token_expiry'); + + if (!$token || ($tokenExpiry && now()->timestamp > $tokenExpiry)) { + return $this->refreshToken(); + } + + return true; + } + + private function refreshToken(): bool + { + $refreshToken = SecureStorage::get('refresh_token'); + + if (!$refreshToken) { + return false; + } + + $response = Http::post('https://your-api.com/api/refresh', [ + 'refresh_token' => $refreshToken + ]); + + if ($response->successful()) { + $data = $response->json(); + SecureStorage::set('api_token', $data['access_token']); + SecureStorage::set('token_expiry', $data['expires_at']); + return true; + } + + return false; + } +} +``` + +This approach ensures your mobile app remains secure while providing seamless access to your backend data through a well-designed API layer. diff --git a/resources/views/docs/mobile/1/concepts/deep-links.md b/resources/views/docs/mobile/1/concepts/deep-links.md new file mode 100644 index 00000000..6a0f91ec --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/deep-links.md @@ -0,0 +1,108 @@ +--- +title: Deep, Universal, App Links and NFC +order: 300 +--- + +## Overview + +NativePHP for Mobile supports both **deep linking** and **web-based linking** into your mobile apps. + +There are two types of link integrations you can configure: + +- **Deep Links** (myapp://some/path) +- **Universal Links (iOS)** and **App Links (Android)** (https://example.net/some/path) + +Each method has its use case, and NativePHP allows you to configure and handle both easily. + +--- + +## Deep Links + +Deep links use a **custom URL scheme** to open your app. + +For example: + +``` +myapp://profile/123 +``` + + +When a user taps a deep link, the mobile operating system detects the custom scheme and opens your app directly. + +### Configuration + +To enable deep linking, you must define: + +- **Scheme**: The protocol (e.g., myapp) +- **Host**: An optional domain-like segment (e.g., open) + +These are configured in your .env: + +```dotenv +NATIVEPHP_DEEPLINK_SCHEME=myapp +NATIVEPHP_DEEPLINK_HOST=open +``` + +## Universal Links (iOS) and App Links (Android) + +Universal Links and App Links allow real HTTPS URLs to open your app instead of a web browser, if the app is installed. + +For example: +```dotenv +https://example.net/property/456 +``` + +When a user taps this link: + + - If your app is installed, it opens directly into the app. + - If not, it opens normally in the browser. + +This provides a seamless user experience without needing a custom scheme. + +### How It Works +1. You must prove to iOS and Android that you own the domain by hosting a special file: + - .well-known/apple-app-site-association (for iOS) + - .well-known/assetlinks.json (for Android) +2. The mobile OS reads these files to verify the link association. +3. Once verified, tapping a real URL will open your app instead of Safari or Chrome. + +NativePHP for Mobile handles all of this for you. + +### Configuration + +To enable Universal Links and App Links, you must define: + +- **Host**: The domain name (e.g., example.net) + +These are configured in your .env: + +```dotenv +NATIVEPHP_DEEPLINK_HOST=example.net +``` + +## Handling Universal/App Links + +Once you've configured your deep link settings, you can handle the link in your app. + +Simply set up a route in your web.php file and the deeplink will redirect to your route. + +```dotenv +https://example.net/profile/123 +``` + +```php +Route::get('/profile/{id}', function ($id) { + // Handle the deep link +}); +``` + +## NFC +NFC is a technology that allows you to read and write NFC tags. + +NativePHP handles NFC tag "bumping" just like a Universal/App Link. +You can use a tool like [NFC Tools](https://www.wakdev.com/en/) to test write NFC tags. + +Set the url to a Universal/App Link and the tag will be written to the NFC tag. +"Bumping" the tag will open the app. + + diff --git a/resources/views/docs/mobile/1/digging-deeper/push-notifications.md b/resources/views/docs/mobile/1/concepts/push-notifications.md similarity index 97% rename from resources/views/docs/mobile/1/digging-deeper/push-notifications.md rename to resources/views/docs/mobile/1/concepts/push-notifications.md index a1b47a3a..fd534659 100644 --- a/resources/views/docs/mobile/1/digging-deeper/push-notifications.md +++ b/resources/views/docs/mobile/1/concepts/push-notifications.md @@ -1,5 +1,5 @@ --- -title: Push Notifications +title: Push Notifications - Firebase order: 400 --- @@ -26,7 +26,7 @@ take a look at how easy it is to listen for a `TokenGenerated` event in Livewire ```php use Livewire\Attributes\On; use Livewire\Component; -use Native\Mobile\Facades\System; +use Native\Mobile\Facades\PushNotifications; use Native\Mobile\Events\PushNotification\TokenGenerated; class PushNotifications extends Component @@ -93,4 +93,4 @@ class PushNotificationController extends Controller SendPushNotification::dispatch($token)->delay(now()->addMinutes(1)); } } -``` +``` \ No newline at end of file diff --git a/resources/views/docs/mobile/1/digging-deeper/security.md b/resources/views/docs/mobile/1/concepts/security.md similarity index 98% rename from resources/views/docs/mobile/1/digging-deeper/security.md rename to resources/views/docs/mobile/1/concepts/security.md index 6b8254b3..2abb9acf 100644 --- a/resources/views/docs/mobile/1/digging-deeper/security.md +++ b/resources/views/docs/mobile/1/concepts/security.md @@ -27,4 +27,4 @@ level of entropy, as this makes them hard to guess and hard to abuse. If your application allows users to connect _their own_ API keys for a service, you should treat these keys with great care. If you choose to store them anywhere (either in a [File](files) or [Database](databases)), make sure you store them -[encrypted](../the-basics/system#encryption-decryption) and decrypt them only when needed. +[encrypted](../the-basics/system#encryption-decryption) and decrypt them only when needed. \ No newline at end of file diff --git a/resources/views/docs/mobile/1/concepts/splash-screen-icons.md b/resources/views/docs/mobile/1/concepts/splash-screen-icons.md new file mode 100644 index 00000000..1a46bed6 --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/splash-screen-icons.md @@ -0,0 +1,359 @@ +--- +title: Splash Screen/Icons +order: 500 +--- + +## Overview + +App icons and splash screens are the first things users see when interacting with your mobile app. NativePHP Mobile makes it easy to customize both elements to create a professional, branded experience. + +## App Icons + +### Basic Icon Setup + +Place a single high-resolution icon file at: `public/icon.png` + +### Requirements +- **Format:** PNG +- **Size:** 1024 Γ— 1024 pixels +- **Shape:** Square +- **Background:** Transparent or solid (your choice) +- **Content:** Should work well at small sizes + +``` +your-laravel-app/ +β”œβ”€β”€ public/ +β”‚ └── icon.png ← Place your 1024x1024 icon here +β”œβ”€β”€ app/ +└── ... +``` + +### Automatic Icon Generation + +NativePHP automatically generates all required icon sizes for both platforms: + +#### iOS Icon Sizes (Generated Automatically) +- App Store: 1024x1024 +- iPhone: 60x60, 120x120, 180x180 +- iPad: 76x76, 152x152, 167x167 +- Settings: 29x29, 58x58, 87x87 +- Spotlight: 40x40, 80x80, 120x120 + +#### Android Icon Sizes (Generated Automatically) +- mdpi: 48x48 +- hdpi: 72x72 +- xhdpi: 96x96 +- xxhdpi: 144x144 +- xxxhdpi: 192x192 + +### Icon Design Best Practices + +```php +// Example of checking if custom icon exists +class IconValidator +{ + public function validateIcon(): array + { + $iconPath = public_path('icon.png'); + + if (!file_exists($iconPath)) { + return ['valid' => false, 'message' => 'Icon file not found']; + } + + $imageInfo = getimagesize($iconPath); + + if (!$imageInfo) { + return ['valid' => false, 'message' => 'Invalid image file']; + } + + [$width, $height] = $imageInfo; + + if ($width !== 1024 || $height !== 1024) { + return [ + 'valid' => false, + 'message' => "Icon must be 1024x1024px, got {$width}x{$height}px" + ]; + } + + if ($imageInfo['mime'] !== 'image/png') { + return ['valid' => false, 'message' => 'Icon must be PNG format']; + } + + return ['valid' => true, 'message' => 'Icon is valid']; + } +} +``` + +#### Design Guidelines +1. **Keep it simple** - Icons must be recognizable at 16x16 pixels +2. **Avoid text** - Text becomes unreadable at small sizes +3. **Use strong contrast** - Ensure visibility on various backgrounds +4. **Make it memorable** - Unique shape or color helps recognition +5. **Test at multiple sizes** - Check how it looks when scaled down +6. **Consider platform conventions** - iOS prefers rounded corners (applied automatically) + +## Splash Screens + +### Splash Screen Configuration + +Splash screens are shown while your app loads. NativePHP provides built-in splash screen support that you can customize. + +### Default Splash Screen + +By default, NativePHP creates a simple splash screen using your app icon and name. No additional configuration required. + +### Custom Splash Screen + +Create custom splash screen assets: + +``` +your-laravel-app/ +β”œβ”€β”€ public/ +β”‚ β”œβ”€β”€ icon.png +β”‚ β”œβ”€β”€ splash-logo.png ← Optional custom splash logo +β”‚ └── splash-background.png ← Optional background image +β”œβ”€β”€ app/ +└── ... +``` + +### Splash Screen Specifications + +#### iOS Launch Images +- **iPhone:** Various sizes for different devices +- **iPad:** Portrait and landscape orientations +- **Safe Areas:** Account for notches and home indicators + +#### Android Splash Screens +- **Centered Logo:** Displayed on colored background +- **Responsive:** Adapts to different screen sizes and orientations +- **Theme-aware:** Can adapt to light/dark themes + +### Configuration Options + +```php +// config/nativephp.php +return [ + 'app' => [ + 'name' => 'Your App Name', + 'splash' => [ + 'background_color' => '#ffffff', + 'logo_path' => 'splash-logo.png', + 'show_loading' => true, + ], + ], +]; +``` + +### Dynamic Splash Screens + +```php +class SplashScreenManager +{ + public function configureSplashScreen(): void + { + $isDarkMode = $this->getUserPreference('dark_mode'); + + $splashConfig = [ + 'background_color' => $isDarkMode ? '#000000' : '#ffffff', + 'logo_path' => $isDarkMode ? 'logo-dark.png' : 'logo-light.png', + 'show_loading' => true, + ]; + + $this->updateSplashConfiguration($splashConfig); + } + + private function getUserPreference(string $key): bool + { + return session($key, false); + } + + private function updateSplashConfiguration(array $config): void + { + // Update runtime splash configuration + config(['nativephp.app.splash' => $config]); + } +} +``` + +## Asset Compilation + +### CSS and JavaScript Assets + +Your mobile app runs locally, so all assets must be compiled before deployment. + +```bash +# Always compile assets before building +npm run build + +# Then build your mobile app +php artisan native:run +``` + +### Build Process Integration + +```json +{ + "scripts": { + "dev": "vite", + "build": "vite build", + "build:mobile": "vite build --mode mobile", + "mobile:prepare": "npm run build:mobile && php artisan optimize" + } +} +``` + +### Asset Optimization for Mobile + +```javascript +// vite.config.js +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + }), + ], + build: { + rollupOptions: { + output: { + manualChunks: { + vendor: ['lodash', 'axios'], + ui: ['@headlessui/vue', '@heroicons/vue'], + } + } + }, + chunkSizeWarningLimit: 1000, + }, +}); +``` + +## Development Workflow + +### Hot Reload (Experimental) + +For development, you can use the experimental watch flag: + +```bash +php artisan native:run --watch +``` + +### Watch Limitations + +Currently supports: +- βœ… Blade templates +- βœ… Livewire components +- βœ… PHP files +- ❌ Compiled CSS/JS assets +- ❌ Vite builds +- ❌ Inertia.js apps + +### Recommended Development Flow + +```bash +# For Blade/Livewire apps +php artisan native:run --watch + +# For apps with compiled assets (Vue, React, Inertia) +npm run dev & # Run in background +npm run build # Build assets +php artisan native:run # Launch app +``` + +## Testing Icons and Splash Screens + +### Icon Testing Checklist + +```php +class AssetTestSuite +{ + public function testIconExists(): void + { + $this->assertFileExists(public_path('icon.png')); + } + + public function testIconDimensions(): void + { + $iconPath = public_path('icon.png'); + [$width, $height] = getimagesize($iconPath); + + $this->assertEquals(1024, $width); + $this->assertEquals(1024, $height); + } + + public function testIconFormat(): void + { + $iconPath = public_path('icon.png'); + $imageInfo = getimagesize($iconPath); + + $this->assertEquals('image/png', $imageInfo['mime']); + } + + public function testSplashConfiguration(): void + { + $splashConfig = config('nativephp.app.splash'); + + $this->assertIsArray($splashConfig); + $this->assertArrayHasKey('background_color', $splashConfig); + } +} +``` + +### Visual Testing + +1. **Test on multiple devices** - Check how icons look on different screen sizes +2. **Test both orientations** - Portrait and landscape modes +3. **Test theme variations** - Light and dark modes +4. **Test loading states** - Ensure splash screens display properly +5. **Performance testing** - Monitor app launch times + +## Platform-Specific Considerations + +### iOS +- Icons automatically get rounded corners +- Supports Dynamic Type for accessibility +- Requires specific launch image sizes +- Splash screens adapt to safe areas + +### Android +- Icons can be adaptive (foreground + background) +- Supports vector drawables for splash screens +- Material Design guidelines apply +- Supports theme-aware splash screens + +### Cross-Platform Assets + +``` +public/ +β”œβ”€β”€ icon.png # Universal app icon +β”œβ”€β”€ icon-android.png # Android-specific (optional) +β”œβ”€β”€ icon-ios.png # iOS-specific (optional) +β”œβ”€β”€ splash-logo.png # Universal splash logo +β”œβ”€β”€ splash-android.png # Android splash (optional) +└── splash-ios.png # iOS splash (optional) +``` + +## Best Practices + +### Icon Design +1. **Start with vector graphics** - Use SVG or AI files for source +2. **Export high quality** - Use 1024x1024 PNG with no compression +3. **Test readability** - Check visibility at 16x16 pixels +4. **Maintain brand consistency** - Match your web/desktop icons +5. **Consider accessibility** - Ensure sufficient contrast + +### Splash Screen Design +1. **Keep it simple** - Splash screens should load quickly +2. **Match your brand** - Use consistent colors and typography +3. **Don't include text** - Text may not scale properly +4. **Consider loading states** - Show progress if app takes time to load +5. **Test performance** - Long splash screens hurt user experience + +### Asset Management +1. **Optimize file sizes** - Compress images without quality loss +2. **Use appropriate formats** - PNG for icons, WebP for photos +3. **Version your assets** - Track changes to visual elements +4. **Automate generation** - Script the creation of multiple sizes +5. **Test regularly** - Verify assets display correctly after changes \ No newline at end of file diff --git a/resources/views/docs/mobile/1/concepts/versioning.md b/resources/views/docs/mobile/1/concepts/versioning.md new file mode 100644 index 00000000..bf6e403c --- /dev/null +++ b/resources/views/docs/mobile/1/concepts/versioning.md @@ -0,0 +1,291 @@ +--- +title: Versioning +order: 600 +--- + +## Overview + +Proper versioning is crucial for mobile app development, affecting app store submissions, user updates, and feature compatibility. NativePHP Mobile provides flexible versioning strategies for both development and production environments. + +## Version Configuration + +### Environment Variables + +Configure your app version in `.env`: + +```bash +# App identifier (must match across all platforms) +NATIVEPHP_APP_ID=com.yourcompany.yourapp + +# Version string (shown to users) +NATIVEPHP_APP_VERSION=1.2.3 + +# Version code (integer, must increase with each release) +NATIVEPHP_APP_VERSION_CODE=123 +``` + +### Dynamic Versioning + +```php +// Set version programmatically +config(['nativephp.app.version' => '1.2.3']); +config(['nativephp.app.version_code' => 123]); +``` + +## Version Types + +### Semantic Versioning (Recommended) + +Follow [Semantic Versioning](https://semver.org/) principles: + +``` +MAJOR.MINOR.PATCH +``` + +- **MAJOR** - Breaking changes or significant new features +- **MINOR** - New features, backward compatible +- **PATCH** - Bug fixes, backward compatible + +Examples: +```bash +NATIVEPHP_APP_VERSION=1.0.0 # Initial release +NATIVEPHP_APP_VERSION=1.1.0 # New features added +NATIVEPHP_APP_VERSION=1.1.1 # Bug fixes +NATIVEPHP_APP_VERSION=2.0.0 # Breaking changes +``` + +### Version Codes + +Version codes are integers that must increase with each release: + +```bash +# v1.0.0 +NATIVEPHP_APP_VERSION_CODE=100 + +# v1.1.0 +NATIVEPHP_APP_VERSION_CODE=110 + +# v1.1.1 +NATIVEPHP_APP_VERSION_CODE=111 + +# v2.0.0 +NATIVEPHP_APP_VERSION_CODE=200 +``` + +## Environment-Specific Versioning + +### Development + +```bash +# .env.development +NATIVEPHP_APP_VERSION=1.2.3-dev +NATIVEPHP_APP_VERSION_CODE=99999 +``` + +### Staging + +```bash +# .env.staging +NATIVEPHP_APP_VERSION=1.2.3-beta +NATIVEPHP_APP_VERSION_CODE=99998 +``` + +### Production + +```bash +# .env.production +NATIVEPHP_APP_VERSION=1.2.3 +NATIVEPHP_APP_VERSION_CODE=123 +``` + +## Automated Versioning + +### Git Tag-Based Versioning + +```bash +#!/bin/bash +# scripts/set-version.sh + +# Get version from git tag +VERSION=$(git describe --tags --exact-match 2>/dev/null || echo "dev") + +# Remove 'v' prefix if present +VERSION=${VERSION#v} + +# Set in environment +export NATIVEPHP_APP_VERSION=$VERSION + +# Generate version code from semantic version +MAJOR=$(echo $VERSION | cut -d. -f1) +MINOR=$(echo $VERSION | cut -d. -f2) +PATCH=$(echo $VERSION | cut -d. -f3) +VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH)) + +export NATIVEPHP_APP_VERSION_CODE=$VERSION_CODE +``` + +### CI/CD Integration + +```yaml +# GitHub Actions +- name: Set version from tag + run: | + if [[ $GITHUB_REF == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + echo "NATIVEPHP_APP_VERSION=$VERSION" >> .env + + # Generate version code + MAJOR=$(echo $VERSION | cut -d. -f1) + MINOR=$(echo $VERSION | cut -d. -f2) + PATCH=$(echo $VERSION | cut -d. -f3) + VERSION_CODE=$((MAJOR * 10000 + MINOR * 100 + PATCH)) + echo "NATIVEPHP_APP_VERSION_CODE=$VERSION_CODE" >> .env + fi +``` + +### Build Number Integration + +```bash +# Use CI build number for development versions +NATIVEPHP_APP_VERSION=1.2.3-build.${BUILD_NUMBER} +NATIVEPHP_APP_VERSION_CODE=${BUILD_NUMBER} +``` + +## Version Management in Code + +### Accessing Current Version + +```php +use Livewire\Component; + +class VersionDisplay extends Component +{ + public function mount() + { + $this->version = config('nativephp.app.version'); + $this->versionCode = config('nativephp.app.version_code'); + } + + public function render() + { + return view('livewire.version-display', [ + 'version' => $this->version, + 'versionCode' => $this->versionCode + ]); + } +} +``` + +### Version Comparison + +```php +class VersionManager +{ + public function isNewerVersion(string $current, string $new): bool + { + return version_compare($new, $current, '>'); + } + + public function getCurrentVersion(): string + { + return config('nativephp.app.version'); + } + + public function checkForUpdates(): array + { + $currentVersion = $this->getCurrentVersion(); + $latestVersion = $this->getLatestVersionFromApi(); + + return [ + 'has_update' => $this->isNewerVersion($currentVersion, $latestVersion), + 'current_version' => $currentVersion, + 'latest_version' => $latestVersion + ]; + } + + private function getLatestVersionFromApi(): string + { + // Check your API for latest version + $response = Http::get('https://api.yourapp.com/version/latest'); + return $response->json('version'); + } +} +``` + +## App Store Considerations + +### iOS App Store + +- **CFBundleShortVersionString** - User-facing version (1.2.3) +- **CFBundleVersion** - Build number (123) +- Version must be incremented for each submission +- Can skip version numbers (1.0 β†’ 1.2 is allowed) + +### Google Play Store + +- **versionName** - User-facing version string +- **versionCode** - Integer that must increase with every release +- Cannot decrease version code +- Can reuse version names with different codes +- **Important**: `NATIVEPHP_APP_VERSION_CODE` must be incremented for each Google Play Store release, even for minor updates + +### Configuration Example + +```php +// config/nativephp.php +return [ + 'app' => [ + 'id' => env('NATIVEPHP_APP_ID', 'com.example.app'), + 'version' => env('NATIVEPHP_APP_VERSION', '1.0.0'), + 'version_code' => env('NATIVEPHP_APP_VERSION_CODE', 1), + ], +]; +``` + +## Best Practices + +### Version Strategy +1. **Use semantic versioning** for consistency +2. **Increment version codes** for every build +3. **Test version upgrades** thoroughly +4. **Document breaking changes** clearly +5. **Plan update strategies** in advance + +### Development Workflow +1. **Branch naming** - Include version in branch names (`feature/v1.2.0-new-ui`) +2. **Tag releases** - Use git tags for version tracking +3. **Automate versioning** - Reduce manual errors +4. **Test backwards compatibility** - Ensure smooth upgrades +5. **Maintain changelog** - Document all changes + +### Release Management +1. **Staged rollouts** - Release to small groups first +2. **Feature flags** - Control feature availability by version +3. **Emergency updates** - Have a fast-track process for critical fixes +4. **Version analytics** - Track version adoption rates +5. **Sunset planning** - Plan when to stop supporting old versions + +### Common Pitfalls +- **Don't decrease version codes** - Google Play Store will reject decreasing `NATIVEPHP_APP_VERSION_CODE` +- **Don't reuse version codes** - Each release must have a unique, higher version code +- **Don't skip testing upgrade paths** - Test how users transition between versions +- **Don't forget to update all platform configs** - Keep versions synchronized +- **Don't ignore app store requirements** - Each platform has specific versioning rules + +### Google Play Store Version Code Requirements + +The `NATIVEPHP_APP_VERSION_CODE` is critical for Google Play Store submissions: + +```bash +# ❌ WRONG - Cannot decrease or reuse version codes +NATIVEPHP_APP_VERSION_CODE=100 # v1.0.0 +NATIVEPHP_APP_VERSION_CODE=99 # v1.0.1 - REJECTED! + +# βœ… CORRECT - Always increase version code +NATIVEPHP_APP_VERSION_CODE=100 # v1.0.0 +NATIVEPHP_APP_VERSION_CODE=101 # v1.0.1 - Accepted +NATIVEPHP_APP_VERSION_CODE=102 # v1.0.2 - Accepted +NATIVEPHP_APP_VERSION_CODE=200 # v2.0.0 - Accepted +``` + +**Remember**: Even for hotfixes or minor updates, the version code must always increase. diff --git a/resources/views/docs/mobile/1/digging-deeper/_index.md b/resources/views/docs/mobile/1/digging-deeper/_index.md deleted file mode 100644 index 7d67168d..00000000 --- a/resources/views/docs/mobile/1/digging-deeper/_index.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Digging Deeper -order: 3 ---- diff --git a/resources/views/docs/mobile/1/digging-deeper/databases.md b/resources/views/docs/mobile/1/digging-deeper/databases.md deleted file mode 100644 index fdccaf8f..00000000 --- a/resources/views/docs/mobile/1/digging-deeper/databases.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Databases -order: 200 ---- - -## Working with Databases - -You'll almost certainly want your application to persist structured data. For this, NativePHP supports -[SQLite](https://sqlite.org/), which works on both iOS and Android devices. - -You can interact with SQLite from PHP in whichever way you're used to. - -## Configuration - -You do not need to do anything special to configure your application to use SQLite. NativePHP will automatically: -- Switch to using SQLite when building your application. -- Create the database for you in the app container. -- Run your migrations each time your app starts, as needed. - -## Migrations - -When writing migrations, you need to consider any special recommendations for working with SQLite. - -For example, prior to Laravel 11, SQLite foreign key constraints are turned off by default. If your application relies -upon foreign key constraints, [you need to enable SQLite support for them](https://laravel.com/docs/database#configuration) before running your migrations. - -**It's important to test your migrations on prod builds before releasing updates!** You don't want to accidentally -delete your user's data when they update your app. - -## Things to note - -- As your app is installed on a separate device, you do not have remote access to the database. -- If a user deletes your application from their device, any databases are also deleted. diff --git a/resources/views/docs/mobile/1/getting-started/_index.md b/resources/views/docs/mobile/1/getting-started/_index.md index 7f67f577..5c684744 100644 --- a/resources/views/docs/mobile/1/getting-started/_index.md +++ b/resources/views/docs/mobile/1/getting-started/_index.md @@ -1,4 +1,4 @@ --- title: Getting Started order: 1 ---- +--- \ No newline at end of file diff --git a/resources/views/docs/mobile/1/getting-started/configuration.md b/resources/views/docs/mobile/1/getting-started/configuration.md index c6cf39b2..2b385d19 100644 --- a/resources/views/docs/mobile/1/getting-started/configuration.md +++ b/resources/views/docs/mobile/1/getting-started/configuration.md @@ -42,11 +42,11 @@ This is useful for removing files like logs or other temporary files. ## Permissions In general, the app stores don't want apps to request permissions that they don't need. -To enable some permissions your app needs you simply need to change their values in the permissions section. +To enable some permissions your app needs, you simply need to change their values in the permissions section. ```dotenv biometric camera nfc -push_notifications +location ``` diff --git a/resources/views/docs/mobile/1/getting-started/environment-setup.md b/resources/views/docs/mobile/1/getting-started/environment-setup.md new file mode 100644 index 00000000..2f955a04 --- /dev/null +++ b/resources/views/docs/mobile/1/getting-started/environment-setup.md @@ -0,0 +1,94 @@ +--- +title: Environment Setup +order: 100 +--- + +## iOS Requirements + +### For iOS +1. macOS (required - iOS development is only possible on Mac) +2. [Xcode 16.0 or later](https://apps.apple.com/app/xcode/id497799835) +3. Xcode Command Line Tools +4. Homebrew (for dependency management) +5. CocoaPods +6. _Optional_ iOS device for testing + +#### Setting up iOS Development Environment + +1. **Install Xcode** + - Download from the [Mac App Store](https://apps.apple.com/app/xcode/id497799835) + - Minimum version: Xcode 16.0 + +2. **Install Xcode Command Line Tools** + ```shell + xcode-select --install + ``` + Verify installation: + ```shell + xcode-select -p + ``` + +3. **Install Homebrew** (if not already installed) + ```shell + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + +4. **Install CocoaPods** + ```shell + brew install cocoapods + ``` + Verify installation: + ```shell + pod --version + ``` + +#### Apple Developer Account +You **do not** need to enroll in the [Apple Developer Program](https://developer.apple.com/programs/enroll/) ($99/year) +to develop and test your apps on a Simulator. However, you will need to enroll when you want to: +- Test your apps on real devices +- Distribute your apps via the App Store + +> **Note** You cannot build iOS apps on Windows or Linux + +## Android Requirements + +### For Android +1. [Android Studio 2024.2.1 or later](https://developer.android.com/studio) +2. Android SDK with API 23 or higher +3. **Windows only**: You must have [7zip](https://www.7-zip.org/) installed. + +> **Note** You do not need to separately install the Java Development Kit (JDK). Android Studio will automatically install the proper JDK for you. + +#### Setting up Android Studio and SDK + +1. **Download and Install Android Studio** + - Download from the [Android Studio download page](https://developer.android.com/studio) + - Minimum version required: Android Studio 2024.2.1 + +2. **Install Android SDK** + - Open Android Studio + - Navigate to **Tools β†’ SDK Manager** + - In the **SDK Platforms** tab, install at least one Android SDK platform for API 23 or higher + - Latest stable version: Android 15 (API 35) + - You only need to install one API version to get started + - In the **SDK Tools** tab, ensure **Android SDK Build-Tools** and **Android SDK Platform-Tools** are installed + +That's it! Android Studio handles all the necessary configuration automatically. + +## Testing on Real Devices + +You don't _need_ a physical iOS/Android device to compile and test your application, as NativePHP for Mobile supports +the iOS Simulator and Android emulators. However, we highly recommend that you test your application on a real device before submitting to the +Apple App Store and Google Play Store. + +### Running on a real device + +#### On iOS +If you want to run your app on a real iOS device, you need to make sure it is in +[Developer Mode](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device) +and that it's been added to your Apple Developer account as +[a registered device](https://developer.apple.com/account/resources/devices/list). + +#### On Android +On Android you need to [enable developer options](https://developer.android.com/studio/debug/dev-options#enable) +and have USB debugging (ADB) enabled. diff --git a/resources/views/docs/mobile/1/getting-started/installation.md b/resources/views/docs/mobile/1/getting-started/installation.md index 6f930534..cf99593e 100644 --- a/resources/views/docs/mobile/1/getting-started/installation.md +++ b/resources/views/docs/mobile/1/getting-started/installation.md @@ -78,6 +78,8 @@ composer require nativephp/mobile ``` *If you experience a cURL error when running this command make sure you are running PHP v8.3+ in your CLI.* +**Windows Performance Tip:** Add `C:\temp` to your Windows Defender exclusions list to significantly speed up composer installs during app compilation. This prevents real-time scanning from slowing down the many temporary files created during the build process. + If this is the first time you're installing the package, you will be prompted to authenticate. Your username is the email address you used when purchasing your license. Your password is your license key. @@ -125,4 +127,4 @@ and that it's been added to your Apple Developer account as On Android you need to [enable developer options](https://developer.android.com/studio/debug/dev-options#enable) and have USB debugging (ADB) enabled. -And that's it! You should now see your Laravel application running as a native app! πŸŽ‰ +And that's it! You should now see your Laravel application running as a native app! πŸŽ‰ \ No newline at end of file diff --git a/resources/views/docs/mobile/1/getting-started/introduction.md b/resources/views/docs/mobile/1/getting-started/introduction.md index c313a037..7957c6a1 100644 --- a/resources/views/docs/mobile/1/getting-started/introduction.md +++ b/resources/views/docs/mobile/1/getting-started/introduction.md @@ -5,22 +5,24 @@ order: 001 ## Welcome to the revolution! -NativePHP for Mobile is a first of its kind library that allows PHP developers to run PHP applications natively on -all sorts of mobile devices _without a web server_. +NativePHP for Mobile is the first library of its kind that lets you run full PHP applications natively on mobile +devices β€” no web server required. -We've combined the statically compiling PHP as an embeddable C library with the flexibility of Laravel and the rich -native APIs of each support platform, unlocking the power and convenience of Laravel for building performant, native -_mobile_ applications using PHP. +By embedding a statically compiled PHP runtime alongside Laravel, and bridging directly into each platform’s native +APIs, NativePHP brings the power of modern PHP to truly native mobile apps. Build performant, offline-capable +experiences using the tools you already know. **It's never been this easy to build beautiful, local-first apps for iOS and Android.** ## Old tools, new tricks -With NativePHP for Mobile, you don't have to learn any new languages or ecosystems. Stop fighting with other package -managers and build tools. Stay in the comfort of PHP and Composer! +With NativePHP for Mobile, you don’t need to learn Swift, Kotlin, or anything new. +No new languages. No unfamiliar build tools. No fighting with Gradle or Xcode. -PHP developers all over the world are building incredible mobile experiences with the skills they already possess. -In just a few hours you can build an app and have it submitted to the app stores for review. +Just PHP. + +Developers around the world are using the skills they already have to build and ship real mobile apps β€” faster than +ever. In just a few hours, you can go from code to app store submission. ## How does it work? @@ -28,8 +30,8 @@ On the simplest level: 1. A statically-compiled version of PHP is bundled with your code into a Swift/Kotlin shell application. 2. NativePHP's custom Swift/Kotlin bridges manage the PHP environment, running your PHP code directly. -3. A custom PHP extension is compiled into PHP, that exposes PHP interfaces to native functions. -4. Your app renders in a native web view, so you can continue developing your UI the way you're used to. +3. A custom PHP extension is compiled into PHP, that exposes PHP interfaces to native functions. +4. Your app renders in a native web view, so you can continue developing your UI the way you're used to. You simply interact with an easy-to-use set of functions from PHP and everything just works! @@ -38,7 +40,7 @@ You simply interact with an easy-to-use set of functions from PHP and everything NativePHP for Mobile is way more than just a web view wrapper for your server-based application. Your application lives _on device_ and is shipped with each installation. -Thanks to our custom PHP extension, you can interact with many native APIs today, with more coming each week, including: +Thanks to our custom PHP extension, you can interact with many native APIs today, with more coming, including: - Camera & Microphone - Biometric ID diff --git a/resources/views/docs/mobile/1/getting-started/roadmap.md b/resources/views/docs/mobile/1/getting-started/roadmap.md index 4899aa01..26e6110a 100644 --- a/resources/views/docs/mobile/1/getting-started/roadmap.md +++ b/resources/views/docs/mobile/1/getting-started/roadmap.md @@ -21,11 +21,13 @@ Presently, NativePHP for Mobile offers the following "native" functionality: - Push Notifications - Deep Links - NFC +- Secure Storage +- Location +- Native image picker +- Splash screen We're working on adding more and more features, including (in no particular order): - - Secure Storage - Microphone access - - Location - Bluetooth - SMS (Android only) - File picker @@ -33,7 +35,6 @@ We're working on adding more and more features, including (in no particular orde - Document scanner - Background tasks - Geofencing - - Native image picker - Calendar access - Local notifications, scheduled notifications - Clipboard API @@ -51,5 +52,4 @@ We're working on adding more and more features, including (in no particular orde - CPU information - Ads - In-app billing - - Splash screen diff --git a/resources/views/docs/mobile/1/the-basics/_index.md b/resources/views/docs/mobile/1/the-basics/_index.md index e8a6458a..373920fa 100644 --- a/resources/views/docs/mobile/1/the-basics/_index.md +++ b/resources/views/docs/mobile/1/the-basics/_index.md @@ -1,4 +1,4 @@ --- title: The Basics order: 2 ---- +--- \ No newline at end of file diff --git a/resources/views/docs/mobile/1/the-basics/app-assets.md b/resources/views/docs/mobile/1/the-basics/app-assets.md index b8346f00..041012df 100644 --- a/resources/views/docs/mobile/1/the-basics/app-assets.md +++ b/resources/views/docs/mobile/1/the-basics/app-assets.md @@ -18,7 +18,7 @@ Place a single high-resolution icon file at: `public/icon.png` - Shape: Square - Background: Transparent or solid β€” your choice -Note: This image will be automatically resized for all Android densities and used as the base iOS app icon. +Note: This image will be automatically resized for all Android densities and used as the base iOS app icon. You must have the GD extension installed and active in your local PHP environment for this to work. --- @@ -49,21 +49,3 @@ This is useful during development for quickly testing changes without rebuilding ### Recommendation Use `--watch` when you're iterating on Blade views or Livewire components. For all other use cases, treat this flag as experimental and optional. - ---- - -## Optional: Installing with ICU Support - -By default, NativePHP installs a smaller PHP runtime without ICU (International Components for Unicode) to keep app size minimal. - -If your Laravel app uses features that rely on `intl` (such as number formatting, localized date handling, or advanced string collation), you’ll need ICU support enabled. - -To include ICU during installation, select it when running: `php artisan native:install`. - -This will install a version of PHP with full ICU support. Note that it increases the PHP binary size significantly (typically from ~16MB to ~44MB). - -**Important:** If you plan to use [Filament](https://filamentphp.com/) in your app, you must enable this option. Filament relies on the `intl` extension for formatting and localization features. - - - - diff --git a/resources/views/docs/mobile/1/the-basics/asynchronous-methods.md b/resources/views/docs/mobile/1/the-basics/asynchronous-methods.md new file mode 100644 index 00000000..4cc36a87 --- /dev/null +++ b/resources/views/docs/mobile/1/the-basics/asynchronous-methods.md @@ -0,0 +1,308 @@ +--- +title: Asynchronous Methods +order: 200 +--- + +## Overview + +Many native mobile operations take time to complete and require user interaction. NativePHP Mobile handles these through asynchronous methods that use Laravel's event system to notify your app when operations complete. + +## Understanding Async vs Sync + +### Synchronous Methods ⚑ +Execute immediately and return results directly. + +```php +// These complete instantly +Haptics::vibrate(); +System::flashlight(); +Dialog::toast('Hello!'); +``` + +### Asynchronous Methods πŸ”„ +Trigger operations that complete later and fire events when done. + +```php +// These trigger operations and fire events when complete +Camera::getPhoto(); // β†’ PhotoTaken event +Biometrics::promptForBiometricID(); // β†’ Completed event +PushNotifications::enrollForPushNotifications(); // β†’ TokenGenerated event +``` + +## Event Handling Pattern + +All asynchronous methods follow the same pattern: + +1. **Call the method** to trigger the operation +2. **Listen for events** to handle the result +3. **Update your UI** based on the outcome + +### Basic Event Structure + +```php +use Livewire\Component; +use Livewire\Attributes\On; +use Native\Mobile\Facades\Camera; +use Native\Mobile\Events\Camera\PhotoTaken; + +class PhotoComponent extends Component +{ + public bool $isCapturing = false; + public ?string $photoPath = null; + + // Step 1: Trigger the async operation + public function takePhoto() + { + $this->isCapturing = true; + Camera::getPhoto(); + } + + // Step 2: Handle the result event + #[On('native:' . PhotoTaken::class)] + public function handlePhotoTaken(string $path) + { + $this->isCapturing = false; + $this->photoPath = $path; + } + + public function render() + { + return view('livewire.photo-component'); + } +} +``` + +## Event Naming Convention + +All frontend events use the `native:` prefix to prevent naming collisions: + +```php +// Backend event class +Native\Mobile\Events\Camera\PhotoTaken + +// Frontend Livewire event (with prefix) +native:Native\Mobile\Events\Camera\PhotoTaken +``` + +## Common Async Operations + +### Camera Operations + +```php +use Native\Mobile\Events\Camera\PhotoTaken; +use Native\Mobile\Events\Gallery\MediaSelected; + +class MediaManager extends Component +{ + public function capturePhoto() + { + Camera::getPhoto(); + } + + public function selectFromGallery() + { + Camera::pickImages('images', true); + } + + #[On('native:' . PhotoTaken::class)] + public function handlePhoto(string $path) + { + // Handle captured photo + } + + #[On('native:' . MediaSelected::class)] + public function handleGallerySelection($success, $files, $count) + { + // Handle selected media + } +} +``` + +### Biometric Authentication + +```php +use Native\Mobile\Events\Biometric\Completed; + +class SecureFeature extends Component +{ + public function authenticate() + { + Biometrics::promptForBiometricID(); + } + + #[On('native:' . Completed::class)] + public function handleBiometric(bool $success) + { + if ($success) { + $this->unlockFeature(); + } else { + $this->showAuthError(); + } + } +} +``` + +### Push Notification Registration + +```php +use Native\Mobile\Events\PushNotification\TokenGenerated; + +class NotificationSetup extends Component +{ + public function enableNotifications() + { + PushNotifications::enrollForPushNotifications(); + } + + #[On('native:' . TokenGenerated::class)] + public function handleToken(string $token) + { + // Send token to your backend + $this->registerToken($token); + } +} +``` + +### Location Services + +```php +use Native\Mobile\Events\Geolocation\LocationReceived; +use Native\Mobile\Events\Geolocation\PermissionStatusReceived; + +class LocationTracker extends Component +{ + public function getCurrentLocation() + { + Geolocation::getCurrentPosition(true); // High accuracy + } + + #[On('native:' . LocationReceived::class)] + public function handleLocation($success = null, $latitude = null, $longitude = null, $accuracy = null, $timestamp = null, $provider = null, $error = null) + { + $this->latitude = $latitude; + $this->longitude = $longitude; + } + + #[On('native:' . PermissionStatusReceived::class)] + public function handlePermissionStatus($location, $coarseLocation, $fineLocation) + { + if (!$coarseLocation == 'granted') { + $this->showLocationPermissionRequest(); + } + } +} +``` + +## Loading States + +Provide visual feedback during async operations: + +```php +class LoadingStatesExample extends Component +{ + public bool $isLoading = false; + public string $loadingMessage = ''; + + public function performAsyncOperation() + { + $this->isLoading = true; + $this->loadingMessage = 'Taking photo...'; + + Camera::getPhoto(); + } + + #[On('native:' . PhotoTaken::class)] + public function handleComplete($path) + { + $this->isLoading = false; + $this->loadingMessage = ''; + + // Process result + } + + public function render() + { + return view('livewire.loading-states-example'); + } +} +``` + +## Advanced Patterns + +### Chaining Async Operations + +```php +class ChainedOperations extends Component +{ + public function authenticateAndCapture() + { + // Step 1: Authenticate first + Biometrics::promptForBiometricID(); + } + + #[On('native:' . Completed::class)] + public function handleAuthComplete(bool $success) + { + if ($success) { + // Step 2: Then capture photo + Camera::getPhoto(); + } + } + + #[On('native:' . PhotoTaken::class)] + public function handlePhotoComplete(string $path) + { + // Step 3: Process the authenticated photo + $this->processSecurePhoto($path); + } +} +``` + +### Multiple Event Listeners + +```php +class MultiEventComponent extends Component +{ + public function performMultipleOperations() + { + Camera::getPhoto(); + PushNotifications::enrollForPushNotifications(); + Geolocation::getCurrentPosition(); + } + + #[On('native:' . PhotoTaken::class)] + public function handlePhoto(string $path) { /* ... */ } + + #[On('native:' . TokenGenerated::class)] + public function handlePushToken(string $token) { /* ... */ } + + #[On('native:' . LocationReceived::class)] + public function handleLocation($success = null, $latitude = null, $longitude = null, $accuracy = null) { /* ... */ } +} +``` + +## Troubleshooting + +### Debugging Async Operations + +```php +class DebuggingComponent extends Component +{ + public function startDebugOperation() + { + Log::info('Starting async operation'); + $this->isLoading = true; + + Camera::getPhoto(); + } + + #[On('native:' . PhotoTaken::class)] + public function handleResult($path) + { + Log::info('Async operation completed', ['result' => $result]); + $this->isLoading = false; + } +} +``` + +Understanding asynchronous methods is crucial for building responsive mobile apps with NativePHP. The event-driven pattern ensures your UI stays responsive while native operations complete in the background. diff --git a/resources/views/docs/mobile/1/the-basics/dialogs.md b/resources/views/docs/mobile/1/the-basics/dialogs.md deleted file mode 100644 index 4ccba9c4..00000000 --- a/resources/views/docs/mobile/1/the-basics/dialogs.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Dialogs -order: 300 ---- - -## Native Dialogs - -NativePHP allows you to trigger many native dialogs. - -Dialogs are created using the `Dialog` facade. - -```php -use Native\Mobile\Facades\Dialog; -``` - -### The Share Dialog - -You may open the native share dialog by using the `Dialog::share()` method. - -```php -Dialog::share('Title', 'Description', 'URL'); -``` - -### The Alert Dialog - -You may open a native alert dialog by using the `Dialog::alert()` method. - -```php -Dialog::alert('Title', 'Message'); -``` - -### The Toast Dialog - -You may open a native toast dialog by using the `Dialog::toast()` method. There is not a toast dialog on iOS, -on iOS we will simply show an Alert Dialog with just an `OK` button. - -```php -Dialog::toast('Message'); -``` diff --git a/resources/views/docs/mobile/1/the-basics/icu-support.md b/resources/views/docs/mobile/1/the-basics/icu-support.md new file mode 100644 index 00000000..2b96e8b7 --- /dev/null +++ b/resources/views/docs/mobile/1/the-basics/icu-support.md @@ -0,0 +1,376 @@ +--- +title: ICU Support +order: 300 +--- + +## Overview + +ICU (International Components for Unicode) is a library that provides robust Unicode and locale support for applications. While NativePHP Mobile includes a lightweight PHP runtime by default, you can optionally enable ICU support for applications that require advanced internationalization features. + +## What is ICU? + +ICU provides: +- **Unicode support** - Full Unicode text processing +- **Localization** - Number, date, and currency formatting +- **Collation** - Language-sensitive string comparison +- **Text boundaries** - Word, sentence, and line breaking +- **Transliteration** - Script conversion between languages + +## When Do You Need ICU? + +### Required for: +- **Number formatting** with locale-specific rules +- **Date/time formatting** with localized patterns +- **Currency formatting** with proper symbols and rules +- **String collation** for sorting in different languages +- **Text normalization** and case conversion +- **Complex text rendering** for right-to-left languages + +### Specifically Required for: +- **[Filament](https://filamentphp.com/)** - Uses `intl` extension extensively +- **Laravel's localization helpers** that depend on `intl` +- **Third-party packages** that require `intl` extension +- **Multi-language applications** with complex formatting needs + +## Installation + +### During Initial Setup + +When running `php artisan native:install`, you'll be prompted: + +```bash +php artisan native:install + +# You'll see: +? βž• Include ICU-enabled PHP binary for Filament/intl requirements? (~30MB extra) + > No (default - smaller app size) + Yes (required for Filament and advanced i18n) +``` + +Select **"Yes"** if you need ICU support. + +### After Installation + +If you already installed without ICU and need to add it: + +```bash +# Re-run the installer and select ICU support +php artisan native:install --force +``` + +## Size Considerations + +| PHP Runtime | Size | Use Case | +|-------------|-------|----------| +| Standard (no ICU) | ~7MB | Basic apps, simple localization | +| With ICU | ~34MB | Filament, complex i18n, number formatting | + +The ICU-enabled runtime is approximately **5x larger**, so only enable it if you specifically need these features. + +## Feature Comparison + +### Without ICU (Default) + +```php +// βœ… Works - Basic functionality +$date = now()->format('Y-m-d'); +$number = number_format(1234.56, 2); + +// ❌ Won't work - Requires intl extension +$formatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY); +$collator = new Collator('en_US'); +``` + +### With ICU Enabled + +```php +// βœ… All basic functionality works +$date = now()->format('Y-m-d'); +$number = number_format(1234.56, 2); + +// βœ… Advanced internationalization works +$formatter = new NumberFormatter('en_US', NumberFormatter::CURRENCY); +$price = $formatter->formatCurrency(1234.56, 'USD'); // $1,234.56 + +$collator = new Collator('en_US'); +$result = $collator->compare('apple', 'Γ€pple'); // Proper Unicode comparison +``` + +## Code Examples + +### Number Formatting + +```php +use NumberFormatter; + +class LocalizedNumbers +{ + public function formatCurrency(float $amount, string $locale, string $currency): string + { + if (!extension_loaded('intl')) { + // Fallback for non-ICU builds + return $currency . ' ' . number_format($amount, 2); + } + + $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); + return $formatter->formatCurrency($amount, $currency); + } + + public function formatPercent(float $value, string $locale): string + { + if (!extension_loaded('intl')) { + return number_format($value * 100, 1) . '%'; + } + + $formatter = new NumberFormatter($locale, NumberFormatter::PERCENT); + return $formatter->format($value); + } +} + +// Usage +$numbers = new LocalizedNumbers(); + +echo $numbers->formatCurrency(1234.56, 'en_US', 'USD'); // $1,234.56 +echo $numbers->formatCurrency(1234.56, 'de_DE', 'EUR'); // 1.234,56 € +echo $numbers->formatPercent(0.1234, 'en_US'); // 12.3% +``` + +### Date Formatting + +```php +use IntlDateFormatter; + +class LocalizedDates +{ + public function formatDate(\DateTime $date, string $locale): string + { + if (!extension_loaded('intl')) { + return $date->format('M j, Y'); + } + + $formatter = new IntlDateFormatter( + $locale, + IntlDateFormatter::LONG, + IntlDateFormatter::NONE + ); + + return $formatter->format($date); + } +} + +// Usage +$dates = new LocalizedDates(); +$date = new DateTime('2024-03-15'); + +echo $dates->formatDate($date, 'en_US'); // March 15, 2024 +echo $dates->formatDate($date, 'de_DE'); // 15. MΓ€rz 2024 +echo $dates->formatDate($date, 'ja_JP'); // 2024εΉ΄3月15ζ—₯ +``` + +### String Collation + +```php +use Collator; + +class LocalizedSorting +{ + public function sortNames(array $names, string $locale): array + { + if (!extension_loaded('intl')) { + // Simple ASCII sort fallback + sort($names); + return $names; + } + + $collator = new Collator($locale); + $collator->sort($names); + return $names; + } +} + +// Usage +$sorter = new LocalizedSorting(); +$names = ['MΓΌller', 'Mueller', 'Miller', 'MΓΆller']; + +$sorted = $sorter->sortNames($names, 'de_DE'); +// Proper German sorting with umlauts +``` + +## Framework Integration + +### Laravel Localization + +```php +// config/app.php +return [ + 'locale' => 'en', + 'available_locales' => ['en', 'es', 'fr', 'de', 'ja'], +]; + +// With ICU support, you can use advanced formatters +class LocalizedContent +{ + public function getLocalizedPrice(float $price): string + { + $locale = app()->getLocale(); + $currency = config('app.currency.' . $locale, 'USD'); + + if (extension_loaded('intl')) { + $formatter = new NumberFormatter($locale, NumberFormatter::CURRENCY); + return $formatter->formatCurrency($price, $currency); + } + + return $currency . ' ' . number_format($price, 2); + } +} +``` + +### Filament Integration + +```php +// Filament requires ICU for proper operation +use Filament\Forms\Components\TextInput; + +TextInput::make('price') + ->numeric() + ->formatStateUsing(function ($state) { + // This formatting requires ICU + $formatter = new NumberFormatter(app()->getLocale(), NumberFormatter::CURRENCY); + return $formatter->formatCurrency($state, 'USD'); + }); +``` + +## Graceful Degradation + +Design your app to work with or without ICU: + +```php +class InternationalizationHelper +{ + public static function hasICU(): bool + { + return extension_loaded('intl'); + } + + public static function formatNumber(float $number, string $locale = 'en_US'): string + { + if (self::hasICU()) { + $formatter = new NumberFormatter($locale, NumberFormatter::DECIMAL); + return $formatter->format($number); + } + + // Fallback formatting + return number_format($number, 2); + } + + public static function compareStrings(string $a, string $b, string $locale = 'en_US'): int + { + if (self::hasICU()) { + $collator = new Collator($locale); + return $collator->compare($a, $b); + } + + // Fallback to simple comparison + return strcmp($a, $b); + } +} +``` + +## Performance Considerations + +### Memory Usage +- ICU adds ~28MB to your app size +- Runtime memory usage increases slightly +- Complex formatting operations are slower + +### Optimization Tips + +```php +class OptimizedFormatting +{ + private static array $formatters = []; + + public static function getCachedFormatter(string $locale, int $style): NumberFormatter + { + $key = $locale . '_' . $style; + + if (!isset(self::$formatters[$key])) { + self::$formatters[$key] = new NumberFormatter($locale, $style); + } + + return self::$formatters[$key]; + } + + public static function formatCurrency(float $amount, string $locale, string $currency): string + { + $formatter = self::getCachedFormatter($locale, NumberFormatter::CURRENCY); + return $formatter->formatCurrency($amount, $currency); + } +} +``` + +## Testing ICU Features + +```php +// tests/Feature/ICUSupportTest.php +namespace Tests\Feature; + +use Tests\TestCase; + +class ICUSupportTest extends TestCase +{ + public function test_icu_extension_loaded() + { + if (config('app.requires_icu')) { + $this->assertTrue(extension_loaded('intl'), 'ICU extension is required but not loaded'); + } + } + + public function test_number_formatting_works() + { + if (!extension_loaded('intl')) { + $this->markTestSkipped('ICU not available'); + } + + $formatter = new \NumberFormatter('en_US', \NumberFormatter::CURRENCY); + $result = $formatter->formatCurrency(1234.56, 'USD'); + + $this->assertEquals('$1,234.56', $result); + } + + public function test_fallback_formatting_works() + { + $helper = new InternationalizationHelper(); + $result = $helper->formatNumber(1234.56); + + $this->assertIsString($result); + $this->assertStringContains('1234', $result); + } +} +``` + +## Decision Matrix + +Use this matrix to decide if you need ICU support: + +| Feature Needed | ICU Required | Alternative | +|----------------|--------------|-------------| +| Basic number formatting | ❌ | `number_format()` | +| Locale-specific currency | βœ… | Manual formatting | +| Date localization | βœ… | Carbon with locales | +| String sorting (non-ASCII) | βœ… | Basic `sort()` | +| Filament admin panel | βœ… | Custom admin | +| Multi-language text processing | βœ… | Limited alternatives | +| Unicode normalization | βœ… | Basic string functions | + +## Best Practices + +1. **Evaluate early** - Decide on ICU support before building your app +2. **Design for fallbacks** - Always provide non-ICU alternatives +3. **Cache formatters** - Reuse NumberFormatter and Collator instances +4. **Test both scenarios** - Test your app with and without ICU +5. **Document requirements** - Clearly state if your app requires ICU +6. **Monitor app size** - Consider the trade-off between features and size +7. **Profile performance** - ICU operations can be slower than simple alternatives + +Choose ICU support if you're building a truly international app, using Filament, or need advanced text processing. For simpler apps, the default lightweight runtime is usually sufficient. diff --git a/resources/views/docs/mobile/1/the-basics/native-functions.md b/resources/views/docs/mobile/1/the-basics/native-functions.md index 3260b32f..f8ec6a08 100644 --- a/resources/views/docs/mobile/1/the-basics/native-functions.md +++ b/resources/views/docs/mobile/1/the-basics/native-functions.md @@ -8,63 +8,13 @@ unique is that it allows you to call native functions from your PHP code. These functions are called from your PHP code using one of an ever-growing list of facades. -Currently, there are two facades available: +All native functionality is namespaced into its own Facade, they are: -- `Native\Mobile\Facades\System` +- `Native\Mobile\Facades\Biometrics` +- `Native\Mobile\Facades\Camera` - `Native\Mobile\Facades\Dialog` - -## System - -The `System` facade is used to call native functions that access system resources. - -For example, you may use the `System::camera()` method to request access to the device's camera. - - -## Synchronous vs. Asynchronous Methods - -It is important to understand the difference between synchronous and asynchronous methods. Some methods -like `flashlight` and `vibrate` are synchronous, meaning that they will block the current thread until the -operation is complete. - -Other methods like `camera` and `biometric` are asynchronous, meaning that they -will return immediately and the operation will be performed in the background. When the operation is -complete, the method will `broadcast an event` to your frontend via an injected javascript event as well -as a traditional [Laravel Event](https://laravel.com/docs/12.x/events#main-content) that you can listen for within your app. - -In order to receive these events, you must register a listener for the event. For example, -take a look at how easy it is to listen for a `PhotoTaken` event in Livewire: - -```php -use Livewire\Attributes\On; -use Livewire\Component; -use Native\Mobile\Facades\System; -use Native\Mobile\Events\Camera\PhotoTaken; - -class Camera extends Component -{ - public string $photoDataUrl = ''; - - public function camera() - { - System::camera(); - } - - #[On('native:' . PhotoTaken::class)] - public function handleCamera($path) - { - $data = base64_encode(file_get_contents($path)); - $mime = mime_content_type($path); - - $this->photoDataUrl = "data:$mime;base64,$data"; - } - - public function render() - { - return view('livewire.system.camera'); - } -} -``` - -All Livewire/front end events are prefixed with `native:` to avoid collisions with other events. -In the following pages we will highlight the available native functions, whether they are asynchronous or not -and which events they fire. +- `Native\Mobile\Facades\Geolocation` +- `Native\Mobile\Facades\Haptics` +- `Native\Mobile\Facades\PushNotifications` +- `Native\Mobile\Facades\SecureStorage` +- `Native\Mobile\Facades\System` diff --git a/resources/views/docs/mobile/1/the-basics/system.md b/resources/views/docs/mobile/1/the-basics/system.md deleted file mode 100644 index 8e87a747..00000000 --- a/resources/views/docs/mobile/1/the-basics/system.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: System -order: 400 ---- - -## Native System - -NativePHP allows you to trigger many native system functions. - -System functions are called using the `System` facade. - -```php -use Native\Mobile\Facades\System; -``` ---- - -# Synchronous Functions ---- - -### Vibration - -You may vibrate the user's device by calling the `vibrate` method: - -```php -System::vibrate() -``` ---- -### Flashlight - -You may toggle the device flashlight (on/off) by calling the `flashlight` method: - -```php -System::flashlight() -``` ---- - -# Asynchronous Functions ---- - -### Camera -```php -Front End Event: `native:Native\Mobile\Events\Camera\PhotoTaken` -Back End Event: `Native\Mobile\Events\Camera\PhotoTaken` -``` - -You may request the native camera interface to take a photograph by calling the `System::camera()` method: - -When the user takes a photograph the event is fired with a payload array that contains one item: `path` -which is a string containing the path to the photo. - -```php -use Native\Mobile\Events\Camera\PhotoTaken; - -System::camera(); - -// Later... -#[On('native:' . PhotoTaken::class)] -public function handlePhotoTaken($path) -{ - $data = base64_encode(file_get_contents($path)); - $mime = mime_content_type($path); - - $this->photoDataUrl = "data:$mime;base64,$data"; -} -``` - -**Note: The first time your application asks to use the camera, the user will be prompted to grant permission. If they -decline, triggering the camera API will silently fail.** - ---- - -### Push Notifications -```php -Front End Event: `native:Native\Mobile\Events\PushNotification\TokenGenerated` -Back End Event: `Native\Mobile\Events\PushNotification\TokenGenerated` -``` -Currently, NativePHP uses [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) to send push notifications to your users. - -Simply use the `enrollForPushNotifications` method to trigger enrolment. If this is the first time that your app tries -to enrol this device for push notifications, the user will be presented with a native alert, allowing them to opt-in. - -Then use the `getPushNotificationsToken` method to retrieve the token. If enrolment was unsuccessful for some reason, -this method will return `null`. - -```php -use Native\Mobile\Events\PushNotification\TokenGenerated; - -System::enrollForPushNotifications(); - -// Later... -#[On('native:' . TokenGenerated::class)] -public function handlePushNotifications(string $token) -{ - // Do something with the token... -} -``` -Once you have the token, you may use it from your server-based applications to trigger Push Notifications directly to -your user's device. - -> Learn more about [what to do with push tokens here](/docs/mobile/1/digging-deeper/push-notifications). - ---- - -### Biometric ID -```php -Front End Event: `native:Native\Mobile\Events\Biometric\Completed` -Back End Event: `Native\Mobile\Events\Biometric\Completed` -``` - -For devices that support some form of biometric identification, you can use this to protect and unlock various parts -of your application. - -```php -use Native\Mobile\Events\Biometric\Completed; - -System::promptForBiometricID() - -// Later... -#[On('native:' . Completed::class)] -public function handleBiometricAuth(boolean $success) -{ - if ($success) { - // Do your super secret activity here - } -} -``` - -Using this, you can gate certain parts of your app, allowing you to offer an extra layer of protection for your user's -data. - -**Note: Despite the name, Biometric identification only gives you *greater confidence* that the person using your app -is *someone* who has the capacity to unlock the device your app is installed on. It does not allow you to *identify* -that user or prove that they are willingly taking this action.** -