// native-bridge.js — Detects if running inside Capacitor native app // and provides native API access (camera for barcode, push notifications, haptics) (function() { 'use strict'; window.NexusNative = { isNative: typeof Capacitor !== 'undefined', _scanStream: null, _scanVideo: null, // Camera barcode scanning — works in native (Capacitor) and web (BarcodeDetector / getUserMedia) async scanBarcode() { // Native Capacitor path if (this.isNative) { try { const { Camera } = await import('@capacitor/camera'); const photo = await Camera.getPhoto({ quality: 90, resultType: 'base64' }); return photo; } catch(e) { return null; } } // Web path: use BarcodeDetector API (Chrome 83+) if (!('BarcodeDetector' in window)) { alert('Tu navegador no soporta escaneo de codigos de barras. Usa Chrome 83+ o un dispositivo movil.'); return null; } return new Promise(async (resolve) => { try { const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: { ideal: 1280 }, height: { ideal: 720 } } }); this._scanStream = stream; // Create overlay UI const overlay = document.createElement('div'); overlay.id = 'barcode-scan-overlay'; overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:99999;display:flex;flex-direction:column;align-items:center;justify-content:center;'; const video = document.createElement('video'); video.autoplay = true; video.playsInline = true; video.style.cssText = 'width:90%;max-width:500px;border-radius:12px;border:3px solid #F5A623;'; video.srcObject = stream; this._scanVideo = video; const label = document.createElement('p'); label.textContent = 'Apunta al codigo de barras...'; label.style.cssText = 'color:#fff;font-size:16px;margin-top:16px;font-family:sans-serif;'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = 'Cancelar'; cancelBtn.style.cssText = 'margin-top:16px;padding:10px 24px;background:#F5A623;color:#000;border:none;border-radius:6px;font-size:15px;cursor:pointer;font-weight:bold;'; cancelBtn.onclick = () => { this.stopScan(); overlay.remove(); resolve(null); }; overlay.appendChild(video); overlay.appendChild(label); overlay.appendChild(cancelBtn); document.body.appendChild(overlay); const detector = new BarcodeDetector({ formats: ['ean_13', 'ean_8', 'code_128', 'code_39', 'qr_code', 'upc_a', 'upc_e'] }); const scanFrame = async () => { if (!this._scanStream) return; try { const barcodes = await detector.detect(video); if (barcodes.length > 0) { const code = barcodes[0].rawValue; label.textContent = 'Codigo detectado: ' + code; label.style.color = '#4CAF50'; // Small delay so user sees the result setTimeout(() => { this.stopScan(); overlay.remove(); resolve(code); }, 400); return; } } catch(e) { /* frame failed, retry */ } requestAnimationFrame(scanFrame); }; // Wait for video to be ready video.onloadedmetadata = () => { video.play(); requestAnimationFrame(scanFrame); }; } catch(e) { console.error('Camera access error:', e); alert('No se pudo acceder a la camara: ' + e.message); resolve(null); } }); }, stopScan() { if (this._scanStream) { this._scanStream.getTracks().forEach(t => t.stop()); this._scanStream = null; } this._scanVideo = null; var overlay = document.getElementById('barcode-scan-overlay'); if (overlay) overlay.remove(); }, // Push notification registration async registerPush() { if (!this.isNative) return null; try { const { PushNotifications } = await import('@capacitor/push-notifications'); const result = await PushNotifications.requestPermissions(); if (result.receive === 'granted') { await PushNotifications.register(); } PushNotifications.addListener('registration', token => { console.log('Push token:', token.value); }); PushNotifications.addListener('pushNotificationReceived', notification => { console.log('Push received:', notification); }); } catch(e) { console.log('Push not available:', e); } }, // Haptic feedback async vibrate() { if (!this.isNative) return; try { const { Haptics, ImpactStyle } = await import('@capacitor/haptics'); await Haptics.impact({ style: ImpactStyle.Light }); } catch(e) {} }, // Status bar (hide for fullscreen POS) async setupStatusBar() { if (!this.isNative) return; try { const { StatusBar, Style } = await import('@capacitor/status-bar'); await StatusBar.setStyle({ style: Style.Dark }); await StatusBar.setBackgroundColor({ color: '#0d0d0d' }); } catch(e) {} } }; // Auto-init if native if (window.NexusNative.isNative) { window.NexusNative.setupStatusBar(); window.NexusNative.registerPush(); } })();