Skip to content

Instantly share code, notes, and snippets.

@shanerbaner82
Created November 18, 2025 04:14
Show Gist options
  • Select an option

  • Save shanerbaner82/f5c606fa3562e5af370c028b5b6d2eb1 to your computer and use it in GitHub Desktop.

Select an option

Save shanerbaner82/f5c606fa3562e5af370c028b5b6d2eb1 to your computer and use it in GitHub Desktop.
<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