Android_Retail/src/Pages/BookingScreen/WeightscaleApp.jsx

541 lines
18 KiB
JavaScript

import React, { useState, useEffect, useRef } from 'react';
import { changeWeightScalePort, changeWeightScaleWeight, GlobalWeightScalePort } from '../../Features/BookingScreen/BookingData/BookingData';
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
const WeightScaleApp = ({ weightscaleclose }) => {
const dispatch = useDispatch();
const weightscaleport = useSelector(GlobalWeightScalePort)
const [weight, setWeight] = useState(null);
const [connected, setConnected] = useState(false);
const [serviceRunning, setServiceRunning] = useState(false);
const [history, setHistory] = useState([]);
const wsRef = useRef(null);
const reconnectTimeoutRef = useRef(null);
const connectionAttempts = useRef(0);
const shouldReconnect = useRef(true);
const isConnecting = useRef(false);
const pollIntervalRef = useRef(null);
const lastStableWeightRef = useRef(null);
const WS_URL = 'ws://localhost:8080';
const CONTROL_URL = 'http://localhost:8081';
useEffect(() => {
// Quick initial check and connect
checkAndConnect();
return () => {
shouldReconnect.current = false;
if (wsRef.current) wsRef.current.close();
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
if (pollIntervalRef.current) clearInterval(pollIntervalRef.current);
};
}, []);
useEffect(() => {
if (weightscaleport) {
startService();
}
else{
stopService();
}
}, [weightscaleport]);
// useEffect(() => {
// if (!connected) {
// weightscaleclose(false);
// }
// }, [connected]);
const checkAndConnect = async () => {
// Quick non-blocking status check
checkServiceStatus().then(status => {
if (status?.running) {
setServiceRunning(true);
shouldReconnect.current = true;
// Connect immediately
setTimeout(() => connectWebSocket(), 50);
}
});
};
const checkServiceStatus = async () => {
try {
const controller = new AbortController();
setTimeout(() => controller.abort(), 800); // Fast timeout
const response = await fetch(`${CONTROL_URL}/status`, { signal: controller.signal });
const data = await response.json();
setServiceRunning(data.running);
return data;
} catch (error) {
return null;
}
};
const openAndroidApp = () => {
try {
const scheme = 'pozoprinter';
const packageName = 'com.example.pozoprinter';
window.location.href = `${scheme}://#Intent;scheme=${scheme};package=${packageName}NewConfigCheckSWeightScaleNewConfigCheckEServiceConditionSstartServiceConditionE;end;`;
} catch (e) {
console.error('Failed to open Android app', e);
}
};
const startService = async () => {
// Immediate UI response
shouldReconnect.current = true;
// Clean up immediately
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
isConnecting.current = false;
connectionAttempts.current = 0;
// Quick status check (non-blocking)
const status = await checkServiceStatus();
if (status?.running) {
// Service already running - connect immediately
setServiceRunning(true);
setTimeout(() => connectWebSocket(), 50);
return;
}
// Try HTTP start (non-blocking, short timeout)
try {
const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
fetch(`${CONTROL_URL}/start`, {
method: 'POST',
signal: controller.signal
})
.then(res => res.json())
.then(data => {
if (data.success) {
setServiceRunning(true);
setTimeout(() => connectWebSocket(), 200);
} else {
// HTTP failed, open Android app
openAndroidApp();
startPolling();
}
})
.catch(() => {
// HTTP failed, open Android app
openAndroidApp();
startPolling();
});
} catch (error) {
openAndroidApp();
startPolling();
}
};
const startPolling = () => {
// Quick polling to detect when service starts
let attempts = 0;
pollIntervalRef.current = setInterval(async () => {
attempts++;
if (attempts > 15) { // 15 seconds max
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
return;
}
const status = await checkServiceStatus();
if (status?.running) {
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
setServiceRunning(true);
setTimeout(() => connectWebSocket(), 100);
}
}, 1000);
};
const stopService = async () => {
// Immediate UI response
shouldReconnect.current = false;
isConnecting.current = false;
// Stop polling immediately
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
// Clear reconnection
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
// Close WebSocket immediately
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
// Update UI immediately
setConnected(false);
weightscaleclose(false);
dispatch(changeWeightScalePort(false));
setWeight(null);
dispatch(changeWeightScaleWeight(null));
setServiceRunning(false);
connectionAttempts.current = 0;
// Send stop request (fire and forget - don't wait)
try {
const controller = new AbortController();
setTimeout(() => controller.abort(), 500);
fetch(`${CONTROL_URL}/stop`, {
method: 'POST',
signal: controller.signal
}).catch(() => {});
} catch (error) {}
};
const connectWebSocket = () => {
if (isConnecting.current || !shouldReconnect.current) return;
if (wsRef.current?.readyState === WebSocket.OPEN ||
wsRef.current?.readyState === WebSocket.CONNECTING) {
return;
}
if (wsRef.current) {
try { wsRef.current.close(); } catch (e) {}
wsRef.current = null;
}
try {
isConnecting.current = true;
const ws = new WebSocket(WS_URL);
wsRef.current = ws;
ws.onopen = () => {
console.log('✅ Connected');
setConnected(true);
setServiceRunning(true);
connectionAttempts.current = 0;
isConnecting.current = false;
if (pollIntervalRef.current) {
clearInterval(pollIntervalRef.current);
pollIntervalRef.current = null;
}
if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current);
reconnectTimeoutRef.current = null;
}
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'weight') {
setWeight(data);
const w = data?.weight;
// Only update global state when the reading is usable
const isValidNumber = typeof w === 'number' && !Number.isNaN(w);
if (
isValidNumber &&
data.stable && // <- only when stable
w !== lastStableWeightRef.current
) {
lastStableWeightRef.current = w;
dispatch(changeWeightScaleWeight(w));
}
setHistory(prev => [data, ...prev].slice(0, 10));
} else if (data.type === 'status') {
setConnected(data.connected);
weightscaleclose(data.connected);
dispatch(changeWeightScalePort(data.connected));
if (data.running !== undefined) setServiceRunning(data.running);
}
} catch (error) {}
};
ws.onerror = () => {
isConnecting.current = false;
};
ws.onclose = () => {
setConnected(false);
weightscaleclose(false);
dispatch(changeWeightScalePort(false));
wsRef.current = null;
isConnecting.current = false;
if (shouldReconnect.current) {
connectionAttempts.current++;
const delay = Math.min(1000 * Math.pow(1.5, connectionAttempts.current), 5000);
reconnectTimeoutRef.current = setTimeout(() => {
if (shouldReconnect.current && !isConnecting.current) {
connectWebSocket();
}
}, delay);
}
};
} catch (error) {
setConnected(false);
weightscaleclose(false);
dispatch(changeWeightScalePort(false));
wsRef.current = null;
isConnecting.current = false;
if (shouldReconnect.current) {
setTimeout(() => {
if (shouldReconnect.current && !isConnecting.current) {
connectWebSocket();
}
}, 1000);
}
}
};
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString();
};
return (
<></>
// <div style={{
// maxWidth: '800px',
// margin: '0 auto',
// padding: '20px',
// fontFamily: 'system-ui, -apple-system, sans-serif'
// }}>
// <div style={{
// background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
// borderRadius: '12px',
// padding: '24px',
// color: 'white',
// marginBottom: '20px',
// boxShadow: '0 4px 6px rgba(0,0,0,0.1)'
// }}>
// <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '12px' }}>
// <h1 style={{ margin: 0, fontSize: '28px', fontWeight: '600' }}>USB Weight Scale</h1>
// <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
// <div style={{
// width: '12px',
// height: '12px',
// borderRadius: '50%',
// backgroundColor: connected ? '#10b981' : '#ef4444',
// boxShadow: connected ? '0 0 8px #10b981' : '0 0 8px #ef4444'
// }} />
// <span style={{ fontSize: '14px', fontWeight: '500' }}>
// {connected ? 'Connected' : 'Disconnected'}
// </span>
// </div>
// </div>
// <p style={{ margin: 0, fontSize: '13px', opacity: 0.9 }}>
// WebSocket: {WS_URL} | Control: {CONTROL_URL}
// </p>
// </div>
// <div style={{
// background: 'white',
// borderRadius: '12px',
// padding: '24px',
// marginBottom: '20px',
// boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
// }}>
// <h2 style={{ margin: '0 0 16px 0', fontSize: '18px', fontWeight: '600' }}>Service Control</h2>
// <div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
// <button
// onClick={startService}
// disabled={serviceRunning}
// style={{
// flex: 1,
// padding: '12px 24px',
// background: serviceRunning ? '#e5e7eb' : 'linear-gradient(135deg, #10b981, #059669)',
// color: serviceRunning ? '#9ca3af' : 'white',
// border: 'none',
// borderRadius: '8px',
// fontSize: '15px',
// fontWeight: '500',
// cursor: serviceRunning ? 'not-allowed' : 'pointer',
// transition: 'all 0.2s'
// }}
// >
// ▶️ Start Service
// </button>
// <button
// onClick={stopService}
// disabled={!serviceRunning}
// style={{
// flex: 1,
// padding: '12px 24px',
// background: !serviceRunning ? '#e5e7eb' : 'linear-gradient(135deg, #ef4444, #dc2626)',
// color: !serviceRunning ? '#9ca3af' : 'white',
// border: 'none',
// borderRadius: '8px',
// fontSize: '15px',
// fontWeight: '500',
// cursor: !serviceRunning ? 'not-allowed' : 'pointer',
// transition: 'all 0.2s'
// }}
// >
// ⏹️ Stop Service
// </button>
// </div>
// <div style={{ fontSize: '14px' }}>
// <span style={{ color: serviceRunning ? '#10b981' : '#6b7280', fontWeight: '500' }}>
// {serviceRunning ? '● Service Running' : '○ Service Stopped'}
// </span>
// {connectionAttempts.current > 0 && !connected && (
// <span style={{ color: '#f59e0b', marginLeft: '12px' }}>
// 🔄 Reconnecting... (attempt {connectionAttempts.current})
// </span>
// )}
// </div>
// </div>
// <div style={{
// background: 'white',
// borderRadius: '12px',
// padding: '32px',
// marginBottom: '20px',
// boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
// minHeight: '200px',
// display: 'flex',
// alignItems: 'center',
// justifyContent: 'center'
// }}>
// {weight ? (
// <div style={{ textAlign: 'center', width: '100%' }}>
// <div style={{ marginBottom: '16px' }}>
// <span style={{ fontSize: '64px', fontWeight: '700', color: '#1f2937' }}>
// {weight.weight.toFixed(2)}
// </span>
// <span style={{ fontSize: '32px', fontWeight: '600', color: '#6b7280', marginLeft: '8px' }}>
// {weight.unit}
// </span>
// </div>
// <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '16px' }}>
// <div style={{
// padding: '6px 12px',
// borderRadius: '6px',
// background: weight.stable ? '#d1fae5' : '#fef3c7',
// color: weight.stable ? '#065f46' : '#92400e',
// fontSize: '13px',
// fontWeight: '500'
// }}>
// {weight.stable ? '✓ Stable' : '⋯ Measuring'}
// </div>
// <span style={{ color: '#9ca3af', fontSize: '13px' }}>
// {formatTime(weight.timestamp)}
// </span>
// </div>
// </div>
// ) : (
// <div style={{ textAlign: 'center', color: '#9ca3af' }}>
// <div style={{ fontSize: '64px', marginBottom: '16px' }}>⚖️</div>
// <p style={{ margin: 0, fontSize: '16px' }}>
// {connected
// ? 'Place item on scale...'
// : serviceRunning
// ? 'Connecting...'
// : 'Click "Start Service" to begin'}
// </p>
// </div>
// )}
// </div>
// {history.length > 0 && (
// <div style={{
// background: 'white',
// borderRadius: '12px',
// padding: '24px',
// marginBottom: '20px',
// boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
// }}>
// <h2 style={{ margin: '0 0 16px 0', fontSize: '18px', fontWeight: '600' }}>Recent Readings</h2>
// <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
// {history.map((reading) => (
// <div
// key={reading.timestamp}
// style={{
// display: 'flex',
// justifyContent: 'space-between',
// alignItems: 'center',
// padding: '12px',
// background: '#f9fafb',
// borderRadius: '8px',
// fontSize: '14px'
// }}
// >
// <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
// <span style={{ fontWeight: '600', color: '#1f2937' }}>
// {reading.weight.toFixed(2)} {reading.unit}
// </span>
// {reading.stable && (
// <span style={{
// padding: '2px 8px',
// borderRadius: '4px',
// background: '#d1fae5',
// color: '#065f46',
// fontSize: '12px',
// fontWeight: '500'
// }}>
// Stable
// </span>
// )}
// </div>
// <span style={{ color: '#9ca3af', fontSize: '13px' }}>
// {formatTime(reading.timestamp)}
// </span>
// </div>
// ))}
// </div>
// </div>
// )}
// {!connected && !serviceRunning && (
// <div style={{
// background: '#fef3c7',
// borderRadius: '12px',
// padding: '24px',
// border: '1px solid #fcd34d'
// }}>
// <h3 style={{ margin: '0 0 12px 0', fontSize: '16px', fontWeight: '600', color: '#92400e' }}>
// 🎯 Service Not Running
// </h3>
// <p style={{ margin: '0 0 12px 0', color: '#78350f', fontSize: '14px' }}>
// Click "Start Service" to connect to the weight scale
// </p>
// <ul style={{ margin: 0, paddingLeft: '20px', color: '#78350f', fontSize: '13px' }}>
// <li>Service will start automatically</li>
// <li>USB scale should be connected to Android device</li>
// <li>WebSocket server on port 8080</li>
// </ul>
// </div>
// )}
// </div>
);
};
export default WeightScaleApp;