Created
November 18, 2025 04:14
-
-
Save shanerbaner82/f5c606fa3562e5af370c028b5b6d2eb1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script setup lang="ts"> | |
| import { Head } from '@inertiajs/vue3'; | |
| import { router } from '@inertiajs/vue3' | |
| import { onMounted, ref } from 'vue'; | |
| import Native from '../../../public/vendor/nativephp-mobile/native'; | |
| import { storeAudio } from '@/actions/App/Http/Controllers/StoreMediaController'; | |
| const results = ref<Record<string, any>>({}); | |
| const loading = ref<Record<string, boolean>>({}); | |
| const audioStatus = ref<string>('idle'); | |
| const recordedAudioFiles = ref< | |
| Array<{ path: string; name: string; url: string }> | |
| >([]); | |
| // Dialog functions | |
| async function showAlert() { | |
| loading.value.alert = true; | |
| try { | |
| await Native.dialog.alert( | |
| 'Confirm Action', | |
| 'Are you sure you want to continue?', | |
| ['Yes', 'No', 'Cancel'], | |
| ); | |
| results.value.alert = { | |
| status: 'Alert shown - check console for button press', | |
| }; | |
| } catch (error: any) { | |
| results.value.alert = { error: error.message }; | |
| } finally { | |
| loading.value.alert = false; | |
| } | |
| } | |
| // QR Code | |
| async function scanQR() { | |
| loading.value.qr = true; | |
| try { | |
| const result = await Native.qrCode.scan(); | |
| results.value.qr = result; | |
| } catch (error: any) { | |
| results.value.qr = { error: error.message }; | |
| } finally { | |
| loading.value.qr = false; | |
| } | |
| } | |
| // Network | |
| async function checkNetwork() { | |
| loading.value.network = true; | |
| try { | |
| const result = await Native.network.status(); | |
| results.value.network = result; | |
| } catch (error: any) { | |
| results.value.network = { error: error.message }; | |
| } finally { | |
| loading.value.network = false; | |
| } | |
| } | |
| // Camera | |
| async function recordVideo() { | |
| loading.value.video = true; | |
| try { | |
| results.value.video = await Native.camera.recordVideo(); | |
| } catch (error: any) { | |
| results.value.video = { error: error.message }; | |
| } finally { | |
| loading.value.video = false; | |
| } | |
| } | |
| // Audio Recording | |
| async function startRecording() { | |
| loading.value.audioStart = true; | |
| try { | |
| await Native.audio.start(); | |
| audioStatus.value = 'recording'; | |
| results.value.audioStart = { status: 'Recording started' }; | |
| } catch (error: any) { | |
| results.value.audioStart = { error: error.message }; | |
| } finally { | |
| loading.value.audioStart = false; | |
| } | |
| } | |
| async function stopRecording() { | |
| loading.value.audioStop = true; | |
| try { | |
| await Native.audio.stop(); | |
| audioStatus.value = 'idle'; | |
| results.value.audioStop = { | |
| status: 'Recording stopped - waiting for file...', | |
| }; | |
| } catch (error: any) { | |
| results.value.audioStop = { error: error.message }; | |
| } finally { | |
| loading.value.audioStop = false; | |
| } | |
| } | |
| async function pauseRecording() { | |
| try { | |
| await Native.audio.pause(); | |
| audioStatus.value = 'paused'; | |
| } catch (error: any) { | |
| console.error('Pause failed:', error); | |
| } | |
| } | |
| async function resumeRecording() { | |
| try { | |
| await Native.audio.resume(); | |
| audioStatus.value = 'recording'; | |
| } catch (error: any) { | |
| console.error('Resume failed:', error); | |
| } | |
| } | |
| async function updateAudioStatus() { | |
| try { | |
| const status = await Native.audio.getStatus(); | |
| audioStatus.value = status; | |
| results.value.audioStatus = status; | |
| } catch (error: any) { | |
| results.value.audioStatus = { error: error.message }; | |
| } | |
| } | |
| // File operations | |
| async function testFileCopy() { | |
| loading.value.fileCopy = true; | |
| try { | |
| results.value.fileCopy = await Native.file.copy( | |
| '/path/from.txt', | |
| '/path/to.txt', | |
| ); | |
| } catch (error: any) { | |
| results.value.fileCopy = { error: error.message }; | |
| } finally { | |
| loading.value.fileCopy = false; | |
| } | |
| } | |
| // Event listeners | |
| const handleAlertButtonPressed = (payload: any) => { | |
| console.log('Alert button pressed:', payload); | |
| results.value.alertEvent = payload; | |
| }; | |
| const handleAudioRecorded = async (payload: any) => { | |
| console.log('Audio recorded:', payload); | |
| const path = payload.path; | |
| router.post(storeAudio({'path': path})); | |
| // | |
| // try { | |
| // results.value.fileCopy = await Native.file.copy( | |
| // path, | |
| // '/path/to.txt', | |
| // ); | |
| // } catch (error: any) { | |
| // results.value.fileCopy = { error: error.message }; | |
| // } finally { | |
| // loading.value.fileCopy = false; | |
| // } | |
| // | |
| // // Move file to public directory via API | |
| // try { | |
| // const response = await fetch('/api/move-audio', { | |
| // method: 'POST', | |
| // headers: { | |
| // 'Content-Type': 'application/json', | |
| // }, | |
| // body: JSON.stringify({ | |
| // path: payload.path, | |
| // mimeType: payload.mimeType || 'audio/m4a', | |
| // id: payload.id, | |
| // }), | |
| // }); | |
| // | |
| // const result = await response.json(); | |
| // | |
| // if (result.success) { | |
| // // Add to recorded files list | |
| // recordedAudioFiles.value.unshift({ | |
| // path: result.publicPath, | |
| // name: result.filename, | |
| // url: result.url, | |
| // }); | |
| // | |
| // results.value.audioRecorded = { | |
| // status: 'Audio saved!', | |
| // file: result.filename, | |
| // }; | |
| // } | |
| // } catch (error: any) { | |
| // console.error('Failed to move audio:', error); | |
| // results.value.audioRecorded = { error: error.message }; | |
| // } | |
| }; | |
| onMounted(() => { | |
| window.Native.on( | |
| 'Native\\Mobile\\Events\\Alert\\ButtonPressed', | |
| handleAlertButtonPressed, | |
| ); | |
| window.Native.on( | |
| 'Native\\Mobile\\Events\\Audio\\AudioRecorded', | |
| handleAudioRecorded, | |
| ); | |
| }); | |
| </script> | |
| <template> | |
| <Head title="Native Functions Demo"> | |
| <link rel="preconnect" href="https://rsms.me/" /> | |
| <link rel="stylesheet" href="https://rsms.me/inter/inter.css" /> | |
| </Head> | |
| <div class="min-h-screen p-6"> | |
| <!-- Header --> | |
| <div class="mx-auto mb-8 max-w-4xl"> | |
| <h1 class="mb-2 text-4xl font-bold text-white"> | |
| NativePHP Functions | |
| </h1> | |
| <p class="text-purple-200"> | |
| Test all available native bridge functions | |
| </p> | |
| </div> | |
| <div class="mx-auto max-w-4xl space-y-4"> | |
| <!-- Dialog Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">💬</span> Dialog | |
| </h2> | |
| <div class="space-y-3"> | |
| <button | |
| @click="showAlert" | |
| :disabled="loading.alert" | |
| class="w-full rounded-xl bg-gradient-to-r from-blue-500 to-blue-600 px-6 py-4 font-semibold text-white shadow-lg transition-all hover:shadow-xl disabled:cursor-not-allowed disabled:opacity-50" | |
| > | |
| {{ loading.alert ? 'Loading...' : 'Show Alert Dialog' }} | |
| </button> | |
| <div | |
| v-if="results.alert" | |
| class="rounded-lg bg-black/30 p-4 font-mono text-sm text-white" | |
| > | |
| {{ results.alert }} | |
| </div> | |
| <div | |
| v-if="results.alertEvent" | |
| class="rounded-lg border border-green-500/50 bg-green-500/20 p-4 text-sm text-green-100" | |
| > | |
| <strong>Button Pressed:</strong> | |
| {{ results.alertEvent.label }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- QR Code Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">📷</span> QR Code | |
| </h2> | |
| <div class="space-y-3"> | |
| <button | |
| @click="scanQR" | |
| :disabled="loading.qr" | |
| class="w-full rounded-xl bg-gradient-to-r from-purple-500 to-purple-600 px-6 py-4 font-semibold text-white shadow-lg transition-all hover:shadow-xl disabled:opacity-50" | |
| > | |
| {{ loading.qr ? 'Opening Scanner...' : 'Scan QR Code' }} | |
| </button> | |
| <div | |
| v-if="results.qr" | |
| class="rounded-lg bg-black/30 p-4 font-mono text-sm text-white" | |
| > | |
| {{ results.qr }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Network Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">📡</span> Network | |
| </h2> | |
| <div class="space-y-3"> | |
| <button | |
| @click="checkNetwork" | |
| :disabled="loading.network" | |
| class="w-full rounded-xl bg-gradient-to-r from-green-500 to-green-600 px-6 py-4 font-semibold text-white shadow-lg transition-all hover:shadow-xl disabled:opacity-50" | |
| > | |
| {{ | |
| loading.network | |
| ? 'Checking...' | |
| : 'Check Network Status' | |
| }} | |
| </button> | |
| <div | |
| v-if="results.network" | |
| class="rounded-lg bg-black/30 p-4 font-mono text-sm text-white" | |
| > | |
| {{ results.network }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Camera Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">🎥</span> Camera | |
| </h2> | |
| <div class="space-y-3"> | |
| <button | |
| @click="recordVideo" | |
| :disabled="loading.video" | |
| class="w-full rounded-xl bg-gradient-to-r from-red-500 to-red-600 px-6 py-4 font-semibold text-white shadow-lg transition-all hover:shadow-xl disabled:opacity-50" | |
| > | |
| {{ | |
| loading.video ? 'Opening Camera...' : 'Record Video' | |
| }} | |
| </button> | |
| <div | |
| v-if="results.video" | |
| class="rounded-lg bg-black/30 p-4 font-mono text-sm text-white" | |
| > | |
| {{ results.video }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Audio Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">🎤</span> Audio Recording | |
| </h2> | |
| <!-- Status Display --> | |
| <div class="mb-6 text-center"> | |
| <div | |
| v-if="audioStatus === 'recording'" | |
| class="animate-pulse text-6xl font-black text-red-500" | |
| style=" | |
| text-shadow: | |
| #ff2d95 0px 0px 20px, | |
| #ff2d95 0px 0px 30px; | |
| " | |
| > | |
| ON-AIR | |
| </div> | |
| <div | |
| v-else-if="audioStatus === 'paused'" | |
| class="text-6xl font-black text-white" | |
| > | |
| PAUSED | |
| </div> | |
| <div v-else class="text-6xl font-black text-white"> | |
| READY | |
| </div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="mb-4 flex justify-center gap-3"> | |
| <button | |
| v-if="audioStatus === 'idle'" | |
| @click="startRecording" | |
| :disabled="loading.audioStart" | |
| class="rounded-xl bg-red-500 p-6 text-white shadow-lg transition-all hover:bg-red-600 hover:shadow-xl disabled:opacity-50" | |
| > | |
| <svg | |
| class="h-10 w-10" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" /> | |
| <path | |
| d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z" | |
| /> | |
| </svg> | |
| </button> | |
| <button | |
| v-if="audioStatus !== 'idle'" | |
| @click=" | |
| audioStatus === 'paused' | |
| ? resumeRecording() | |
| : pauseRecording() | |
| " | |
| class="rounded-xl bg-cyan-500 p-6 text-white shadow-lg transition-all hover:bg-cyan-600 hover:shadow-xl" | |
| > | |
| <svg | |
| v-if="audioStatus === 'paused'" | |
| class="h-10 w-10" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" | |
| /> | |
| </svg> | |
| <svg | |
| v-else | |
| class="h-10 w-10" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| d="M5.75 3a.75.75 0 00-.75.75v12.5c0 .414.336.75.75.75h1.5a.75.75 0 00.75-.75V3.75A.75.75 0 007.25 3h-1.5zM12.75 3a.75.75 0 00-.75.75v12.5c0 .414.336.75.75.75h1.5a.75.75 0 00.75-.75V3.75a.75.75 0 00-.75-.75h-1.5z" | |
| /> | |
| </svg> | |
| </button> | |
| <button | |
| v-if="audioStatus !== 'idle'" | |
| @click="stopRecording" | |
| :disabled="loading.audioStop" | |
| class="rounded-xl bg-gray-600 p-6 text-white shadow-lg transition-all hover:bg-gray-700 hover:shadow-xl disabled:opacity-50" | |
| > | |
| <svg | |
| class="h-10 w-10" | |
| fill="currentColor" | |
| viewBox="0 0 20 20" | |
| > | |
| <path | |
| fill-rule="evenodd" | |
| d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 00-1 1v4a1 1 0 001 1h4a1 1 0 001-1V8a1 1 0 00-1-1H8z" | |
| clip-rule="evenodd" | |
| /> | |
| </svg> | |
| </button> | |
| </div> | |
| <!-- Results --> | |
| <div | |
| v-if="results.audioRecorded" | |
| class="mt-3 rounded-lg border border-green-500/50 bg-green-500/20 p-4 text-sm text-green-100" | |
| > | |
| <strong>✓ {{ results.audioRecorded.status }}</strong> | |
| <div v-if="results.audioRecorded.file" class="mt-1 text-xs"> | |
| {{ results.audioRecorded.file }} | |
| </div> | |
| </div> | |
| <!-- Recorded Files List --> | |
| <div | |
| v-if="recordedAudioFiles.length > 0" | |
| class="mt-6 space-y-2" | |
| > | |
| <h3 class="mb-3 text-lg font-semibold text-white"> | |
| Recordings ({{ recordedAudioFiles.length }}) | |
| </h3> | |
| <div | |
| v-for="file in recordedAudioFiles" | |
| :key="file.path" | |
| class="rounded-lg bg-black/30 p-3" | |
| > | |
| <div class="mb-2 font-mono text-sm text-white"> | |
| {{ file.name }} | |
| </div> | |
| <audio :src="file.url" controls class="w-full"></audio> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- File Operations Section --> | |
| <div | |
| class="rounded-2xl border border-white/20 bg-white/10 p-6 backdrop-blur-lg" | |
| > | |
| <h2 | |
| class="mb-4 flex items-center gap-2 text-2xl font-bold text-white" | |
| > | |
| <span class="text-3xl">📁</span> File Operations | |
| </h2> | |
| <div class="space-y-3"> | |
| <button | |
| @click="testFileCopy" | |
| :disabled="loading.fileCopy" | |
| class="w-full rounded-xl bg-gradient-to-r from-yellow-500 to-yellow-600 px-6 py-4 font-semibold text-white shadow-lg transition-all hover:shadow-xl disabled:opacity-50" | |
| > | |
| {{ loading.fileCopy ? 'Copying...' : 'Test File Copy' }} | |
| </button> | |
| <div | |
| v-if="results.fileCopy" | |
| class="rounded-lg bg-black/30 p-4 font-mono text-sm text-white" | |
| > | |
| {{ results.fileCopy }} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="mx-auto mt-8 max-w-4xl text-center text-sm text-purple-300"> | |
| <p>NativePHP Mobile Bridge Demo • Built with Vue 3 & Inertia</p> | |
| </div> | |
| </div> | |
| </template> | |
| <style scoped> | |
| body { | |
| font-family: | |
| 'Inter', | |
| system-ui, | |
| -apple-system, | |
| sans-serif; | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment