ai prediction detil screen added

This commit is contained in:
yashwin-foxy 2025-08-13 18:21:57 +05:30
parent 413a1d74de
commit 692a8156da
11 changed files with 1746 additions and 14 deletions

View File

@ -0,0 +1,502 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DICOM Viewer</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background: rgb(8, 0, 0);
font-family: Arial, sans-serif;
}
#dicomImage {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: white;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #2196F3;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #F44336;
text-align: center;
padding: 20px;
}
.success {
color: #4CAF50;
text-align: center;
padding: 20px;
}
.debug {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
max-width: 300px;
z-index: 1000;
}
</style>
</head>
<body>
<div id="dicomImage">
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Viewer...</div>
</div>
</div>
<div id="debug" class="debug" style="display: none;">
<strong>Debug Info:</strong><br>
<div id="debugContent"></div>
</div>
<script>
// Global variables
let cornerstone = null;
let cornerstoneWADOImageLoader = null;
let dicomParser = null;
let isLoaded = false;
let currentDicomUrl = null;
let debugMode = true;
// Debug logging function
function debugLog(message, type = 'info') {
if (debugMode) {
console.log(`[DICOM Viewer] ${message}`);
const debugContent = document.getElementById('debugContent');
if (debugContent) {
const timestamp = new Date().toLocaleTimeString();
debugContent.innerHTML += `<div>[${timestamp}] ${message}</div>`;
}
}
}
// Show debug panel
function showDebug() {
if (debugMode) {
document.getElementById('debug').style.display = 'block';
}
}
// Function to load external libraries with retry
async function loadLibraries() {
try {
debugLog('Starting library loading process...');
showDebug();
// Load DICOM Parser first
await loadDicomParser();
// Load Cornerstone Core
await loadCornerstoneCore();
// Load Cornerstone WADO Image Loader with fallback
await loadCornerstoneWADO();
// Initialize viewer
initializeViewer();
// If we already have a DICOM URL, load it
if (currentDicomUrl) {
loadDicomImage(currentDicomUrl);
}
} catch (error) {
debugLog(`Error loading libraries: ${error.message}`, 'error');
showError(`Failed to load DICOM viewer libraries: ${error.message}`);
}
}
// Load DICOM Parser
function loadDicomParser() {
return new Promise((resolve, reject) => {
debugLog('Loading DICOM Parser...');
const dicomParserScript = document.createElement('script');
dicomParserScript.src = 'https://unpkg.com/dicom-parser@1.8.6/dist/dicomParser.min.js';
dicomParserScript.onload = () => {
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
dicomParserScript.onerror = (error) => {
debugLog(`Failed to load DICOM Parser: ${error}`, 'error');
reject(new Error('Failed to load DICOM Parser'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('DICOM Parser loading timeout'));
}, 10000);
dicomParserScript.onload = () => {
clearTimeout(timeout);
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
document.head.appendChild(dicomParserScript);
});
}
// Load Cornerstone Core
function loadCornerstoneCore() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone Core...');
const cornerstoneScript = document.createElement('script');
cornerstoneScript.src = 'https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.js';
cornerstoneScript.onload = () => {
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
cornerstoneScript.onerror = (error) => {
debugLog(`Failed to load Cornerstone Core: ${error}`, 'error');
reject(new Error('Failed to load Cornerstone Core'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('Cornerstone Core loading timeout'));
}, 10000);
cornerstoneScript.onload = () => {
clearTimeout(timeout);
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
document.head.appendChild(cornerstoneScript);
});
}
// Load Cornerstone WADO Image Loader with fallback
function loadCornerstoneWADO() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone WADO Image Loader...');
// Try multiple sources for WADO loader
const wadoSources = [
'https://unpkg.com/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.16.0/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.15.0/dist/cornerstoneWADOImageLoader.js',
'https://cdn.jsdelivr.net/npm/cornerstone-wado-image-loader@4.0.1/dist/cornerstoneWADOImageLoader.js'
];
let currentSourceIndex = 0;
function tryNextSource() {
if (currentSourceIndex >= wadoSources.length) {
reject(new Error('All WADO Image Loader sources failed'));
return;
}
const currentSource = wadoSources[currentSourceIndex];
debugLog(`Trying WADO source ${currentSourceIndex + 1}: ${currentSource}`);
const wadoScript = document.createElement('script');
wadoScript.src = currentSource;
wadoScript.onload = () => {
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
wadoScript.onerror = (error) => {
debugLog(`Failed to load WADO from ${currentSource}: ${JSON.stringify(error)}`, 'error');
currentSourceIndex++;
tryNextSource();
};
// Set timeout for loading
const timeout = setTimeout(() => {
debugLog(`WADO loader timeout from ${currentSource}`, 'error');
currentSourceIndex++;
tryNextSource();
}, 8000);
wadoScript.onload = () => {
clearTimeout(timeout);
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
document.head.appendChild(wadoScript);
}
tryNextSource();
});
}
// Initialize the viewer
function initializeViewer() {
try {
const element = document.getElementById('dicomImage');
cornerstone.enable(element);
isLoaded = true;
debugLog('Cornerstone viewer initialized successfully');
} catch (error) {
debugLog(`Error initializing viewer: ${error.message}`, 'error');
showError(`Failed to initialize DICOM viewer: ${error.message}`);
}
}
// Load DICOM image with better error handling
async function loadDicomImage(dicomUrl) {
if (!isLoaded) {
debugLog('Libraries not loaded yet, storing URL');
currentDicomUrl = dicomUrl;
return;
}
try {
showLoading();
debugLog(`Loading DICOM image from: ${dicomUrl}`);
// Test if URL is accessible first
await testUrlAccessibility(dicomUrl);
// Try to load with DICOM Parser first for validation
try {
await validateDicomWithParser(dicomUrl);
debugLog('DICOM file validated with parser');
} catch (parserError) {
debugLog(`DICOM parser validation failed: ${parserError.message}`, 'warning');
// Continue anyway, as Cornerstone might still be able to handle it
}
const element = document.getElementById('dicomImage');
const image = await cornerstone.loadImage(`wadouri:${dicomUrl}`);
cornerstone.displayImage(element, image);
showSuccess('DICOM image loaded successfully');
debugLog('DICOM image loaded successfully');
// Send success message to React Native
sendMessageToReactNative({
type: 'success',
message: 'DICOM image loaded successfully'
});
} catch (error) {
debugLog(`Error loading DICOM image: ${error.message}`, 'error');
showError(`Failed to load DICOM image: ${error.message}`);
// Send error message to React Native
sendMessageToReactNative({
type: 'error',
message: `Failed to load DICOM image: ${error.message}`
});
}
}
// Validate DICOM file with parser
async function validateDicomWithParser(url) {
try {
debugLog('Validating DICOM file with parser...');
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
if (!dicomParser) {
throw new Error('DICOM Parser not available');
}
const dataSet = dicomParser.parseDicom(arrayBuffer);
debugLog(`DICOM file validated. Patient: ${dataSet.string('x00100010') || 'Unknown'}`);
debugLog(`Modality: ${dataSet.string('x00080060') || 'Unknown'}`);
debugLog(`Study Date: ${dataSet.string('x00080020') || 'Unknown'}`);
return dataSet;
} catch (error) {
debugLog(`DICOM parser validation failed: ${error.message}`, 'warning');
throw error;
}
}
// Test URL accessibility
async function testUrlAccessibility(url) {
try {
debugLog(`Testing URL accessibility: ${url}`);
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
debugLog(`Content-Type: ${contentType}`);
if (contentType && !contentType.includes('application/dicom') && !contentType.includes('image/')) {
debugLog(`Warning: Unexpected content type: ${contentType}`);
}
} catch (error) {
debugLog(`URL accessibility test failed: ${error.message}`, 'error');
throw new Error(`URL not accessible: ${error.message}`);
}
}
// Send message to React Native
function sendMessageToReactNative(data) {
if (window.ReactNativeWebView) {
try {
const message = JSON.stringify(data);
window.ReactNativeWebView.postMessage(message);
debugLog(`Message sent to React Native: ${message}`);
} catch (error) {
debugLog(`Failed to send message to React Native: ${error.message}`, 'error');
}
} else {
debugLog('ReactNativeWebView not available');
}
}
// Show loading state
function showLoading() {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Image...</div>
</div>
`;
}
// Show error state
function showError(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="error">
<h3>Error</h3>
<p>${message}</p>
<p>Please check the DICOM URL and try again.</p>
<button onclick="retryLoading()" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 5px; margin-top: 10px;">Retry</button>
</div>
`;
}
// Show success state
function showSuccess(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="success">
<h3>Success</h3>
<p>${message}</p>
</div>
`;
// Clear success message after 2 seconds
setTimeout(() => {
if (element.querySelector('.success')) {
element.innerHTML = '';
}
}, 2000);
}
// Retry loading
function retryLoading() {
if (currentDicomUrl) {
debugLog('Retrying DICOM image loading...');
loadDicomImage(currentDicomUrl);
}
}
// Listen for messages from React Native
window.addEventListener('message', function(event) {
try {
const dicomUrl = event.data;
debugLog(`Received DICOM URL from React Native: ${dicomUrl}`);
if (dicomUrl && typeof dicomUrl === 'string') {
currentDicomUrl = dicomUrl;
if (isLoaded) {
loadDicomImage(dicomUrl);
}
}
} catch (error) {
debugLog(`Error processing message from React Native: ${error.message}`, 'error');
showError('Invalid message received from app');
}
});
// Start loading libraries when page loads
window.addEventListener('load', () => {
debugLog('Page loaded, starting library loading...');
loadLibraries();
});
// Send ready message to React Native
sendMessageToReactNative({
type: 'ready',
message: 'DICOM viewer HTML loaded with DICOM Parser support'
});
// Global error handler
window.addEventListener('error', function(event) {
debugLog(`Global error: ${event.error?.message || event.message}`, 'error');
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(event) {
debugLog(`Unhandled promise rejection: ${event.reason}`, 'error');
});
</script>
</body>
</html>

View File

@ -0,0 +1,443 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DICOM Viewer Test</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f5f5f5;
font-family: Arial, sans-serif;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.test-section {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.url-input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
.load-button {
background: #2196F3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.load-button:hover {
background: #1976D2;
}
.viewer-container {
margin-top: 20px;
border: 2px solid #ddd;
border-radius: 8px;
overflow: hidden;
min-height: 400px;
}
.status {
padding: 10px;
background: #f0f0f0;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
#dicomImage {
width: 100%;
height: 400px;
background: #000;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.error {
color: #F44336;
background: #FFEBEE;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success {
color: #4CAF50;
background: #E8F5E8;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.warning {
color: #FF9800;
background: #FFF3E0;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.sample-urls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.sample-url {
background: #E3F2FD;
padding: 10px;
border-radius: 4px;
cursor: pointer;
border: 1px solid #BBDEFB;
}
.sample-url:hover {
background: #BBDEFB;
}
.dicom-info {
background: #F5F5F5;
padding: 15px;
border-radius: 4px;
margin: 15px 0;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>DICOM Viewer Test</h1>
<p>Test the DICOM viewer functionality in your browser before using it in React Native.</p>
<div class="test-section">
<h3>Sample DICOM URLs</h3>
<div class="sample-urls">
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm')">
<strong>Sample 1:</strong><br>
LIDC-IDRI-0001
</div>
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm')">
<strong>Sample 2:</strong><br>
LIDC-IDRI-0001
</div>
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm')">
<strong>Sample 3:</strong><br>
LIDC-IDRI-0001
</div>
</div>
</div>
<div class="test-section">
<h3>Custom DICOM URL</h3>
<input type="text" id="customUrl" class="url-input" placeholder="Enter DICOM URL here..." />
<button onclick="loadCustomUrl()" class="load-button">Load DICOM Image</button>
</div>
<div class="viewer-container">
<div class="status" id="status">Ready to load DICOM image</div>
<div id="dicomImage">
<div>Click a sample URL above or enter a custom URL to load a DICOM image</div>
</div>
</div>
<div id="dicomInfo" class="dicom-info" style="display: none;">
<strong>DICOM Information:</strong><br>
<div id="dicomInfoContent"></div>
</div>
<div id="messages"></div>
</div>
<script>
let cornerstone = null;
let cornerstoneWADOImageLoader = null;
let dicomParser = null;
let isLoaded = false;
// Load sample URL
function loadSampleUrl(url) {
document.getElementById('customUrl').value = url;
loadDicomImage(url);
}
// Load custom URL
function loadCustomUrl() {
const url = document.getElementById('customUrl').value.trim();
if (url) {
loadDicomImage(url);
} else {
showMessage('Please enter a valid URL', 'error');
}
}
// Show message
function showMessage(message, type = 'info') {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.className = type;
messageDiv.textContent = message;
messagesDiv.appendChild(messageDiv);
// Remove message after 5 seconds
setTimeout(() => {
messageDiv.remove();
}, 5000);
}
// Update status
function updateStatus(message) {
document.getElementById('status').textContent = message;
}
// Show DICOM info
function showDicomInfo(info) {
const infoDiv = document.getElementById('dicomInfo');
const contentDiv = document.getElementById('dicomInfoContent');
if (info) {
contentDiv.innerHTML = info;
infoDiv.style.display = 'block';
} else {
infoDiv.style.display = 'none';
}
}
// Load libraries
async function loadLibraries() {
try {
updateStatus('Loading DICOM viewer libraries...');
// Load DICOM Parser first
await loadScript('https://unpkg.com/dicom-parser@1.8.6/dist/dicomParser.min.js');
dicomParser = window.dicomParser;
showMessage('DICOM Parser loaded successfully', 'success');
// Load Cornerstone Core
await loadScript('https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.js');
cornerstone = window.cornerstone;
// Load Cornerstone WADO Image Loader with fallback
await loadCornerstoneWADO();
isLoaded = true;
updateStatus('Libraries loaded successfully');
showMessage('All DICOM viewer libraries loaded successfully', 'success');
// Initialize viewer
const element = document.getElementById('dicomImage');
cornerstone.enable(element);
} catch (error) {
updateStatus('Failed to load libraries');
showMessage(`Failed to load libraries: ${error.message}`, 'error');
}
}
// Load script
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
// Load Cornerstone WADO Image Loader with fallback
function loadCornerstoneWADO() {
return new Promise((resolve, reject) => {
updateStatus('Loading Cornerstone WADO Image Loader...');
// Try multiple sources for WADO loader
const wadoSources = [
'https://unpkg.com/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.16.0/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.15.0/dist/cornerstoneWADOImageLoader.js',
'https://cdn.jsdelivr.net/npm/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js'
];
let currentSourceIndex = 0;
function tryNextSource() {
if (currentSourceIndex >= wadoSources.length) {
reject(new Error('All WADO Image Loader sources failed'));
return;
}
const currentSource = wadoSources[currentSourceIndex];
updateStatus(`Trying WADO source ${currentSourceIndex + 1}: ${currentSource.split('/').pop()}`);
const wadoScript = document.createElement('script');
wadoScript.src = currentSource;
wadoScript.onload = () => {
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
showMessage(`WADO Image Loader loaded successfully from: ${currentSource.split('/').pop()}`, 'success');
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
showMessage(`WADO loader initialization failed: ${error.message}`, 'warning');
currentSourceIndex++;
tryNextSource();
}
};
wadoScript.onerror = (error) => {
showMessage(`Failed to load WADO from ${currentSource.split('/').pop()}`, 'warning');
currentSourceIndex++;
tryNextSource();
};
// Set timeout for loading
const timeout = setTimeout(() => {
showMessage(`WADO loader timeout from ${currentSource.split('/').pop()}`, 'warning');
currentSourceIndex++;
tryNextSource();
}, 8000);
wadoScript.onload = () => {
clearTimeout(timeout);
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
showMessage(`WADO Image Loader loaded successfully from: ${currentSource.split('/').pop()}`, 'success');
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
showMessage(`WADO loader initialization failed: ${error.message}`, 'warning');
currentSourceIndex++;
tryNextSource();
}
};
document.head.appendChild(wadoScript);
}
tryNextSource();
});
}
// Load DICOM image
async function loadDicomImage(url) {
if (!isLoaded) {
showMessage('Libraries not loaded yet, please wait...', 'error');
return;
}
try {
updateStatus('Loading DICOM image...');
showMessage(`Loading DICOM image from: ${url}`, 'info');
// Test URL accessibility
await testUrl(url);
// Validate DICOM with parser
let dicomInfo = null;
try {
dicomInfo = await validateDicomWithParser(url);
showMessage('DICOM file validated successfully', 'success');
} catch (parserError) {
showMessage(`DICOM validation warning: ${parserError.message}`, 'warning');
}
const element = document.getElementById('dicomImage');
const image = await cornerstone.loadImage(`wadouri:${url}`);
cornerstone.displayImage(element, image);
updateStatus('DICOM image loaded successfully');
showMessage('DICOM image loaded successfully!', 'success');
// Display DICOM information if available
if (dicomInfo) {
displayDicomInfo(dicomInfo);
}
} catch (error) {
updateStatus('Failed to load DICOM image');
showMessage(`Failed to load DICOM image: ${error.message}`, 'error');
showDicomInfo(null);
}
}
// Validate DICOM with parser
async function validateDicomWithParser(url) {
try {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
if (!dicomParser) {
throw new Error('DICOM Parser not available');
}
const dataSet = dicomParser.parseDicom(arrayBuffer);
return dataSet;
} catch (error) {
throw new Error(`DICOM validation failed: ${error.message}`);
}
}
// Display DICOM information
function displayDicomInfo(dataSet) {
try {
const info = [
`Patient Name: ${dataSet.string('x00100010') || 'Unknown'}`,
`Patient ID: ${dataSet.string('x00100020') || 'Unknown'}`,
`Modality: ${dataSet.string('x00080060') || 'Unknown'}`,
`Study Date: ${dataSet.string('x00080020') || 'Unknown'}`,
`Study Description: ${dataSet.string('x00081030') || 'Unknown'}`,
`Manufacturer: ${dataSet.string('x00080070') || 'Unknown'}`,
`Image Size: ${dataSet.uint16('x00280010') || 'Unknown'} x ${dataSet.uint16('x00280011') || 'Unknown'}`,
`Bits Allocated: ${dataSet.uint16('x00280100') || 'Unknown'}`,
`Samples per Pixel: ${dataSet.uint16('x00280002') || 'Unknown'}`
].join('<br>');
showDicomInfo(info);
} catch (error) {
console.error('Error displaying DICOM info:', error);
showDicomInfo('Error displaying DICOM information');
}
}
// Test URL accessibility
async function testUrl(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
console.log('Content-Type:', contentType);
if (contentType && !contentType.includes('application/dicom') && !contentType.includes('image/')) {
console.warn('Warning: Unexpected content type:', contentType);
}
} catch (error) {
throw new Error(`URL not accessible: ${error.message}`);
}
}
// Initialize when page loads
window.addEventListener('load', loadLibraries);
</script>
</body>
</html>

View File

@ -13,7 +13,7 @@ import { theme } from '../../../theme';
// Import screens
import { AIPredictionsScreen, AIPredictionDetailScreen } from '../screens';
import { ComingSoonScreen } from '../../../shared/components';
import { ComingSoonScreen, DicomViewer } from '../../../shared/components';
// Import types
import type { AIPredictionStackParamList } from './navigationTypes';
@ -152,7 +152,7 @@ const AIPredictionStackNavigator: React.FC = () => {
{/* AI Prediction Details Screen */}
<Stack.Screen
name="AIPredictionDetails"
component={AIPredictionDetailScreen}
component={() => <DicomViewer dicomUrl={'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'} />}
options={({ navigation, route }) => ({
title: 'Create Suggestion',
headerLeft: () => (

View File

@ -543,8 +543,8 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
<TouchableWithoutFeedback onPress={closeAllDropdowns}>
<>
<TouchableWithoutFeedback onPress={closeAllDropdowns} disabled={!showSuggestionTypeDropdown && !showPriorityDropdown}>
<View style={{flex:1}}>
{/* Enhanced Header */}
{renderHeader()}
@ -860,7 +860,7 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
</ScrollView>
</KeyboardAvoidingView>
</View>
</>
</View>
</TouchableWithoutFeedback>

View File

@ -470,15 +470,15 @@ const PatientsScreen: React.FC<PatientsScreenProps> = ({ navigation }) => {
/>
}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={8}
getItemLayout={(data, index) => ({
length: 120, // Approximate height of PatientCard
offset: 120 * index,
index,
})}
// removeClippedSubviews={true}
// maxToRenderPerBatch={10}
// windowSize={10}
// initialNumToRender={8}
// getItemLayout={(data, index) => ({
// length: 120, // Approximate height of PatientCard
// offset: 120 * index,
// index,
// })}
/>
</SafeAreaView>
);

View File

@ -0,0 +1,207 @@
# DICOM Viewer Component
## Overview
The DICOM Viewer component is a React Native component that uses WebView to display DICOM medical imaging files. It integrates with Cornerstone.js and Cornerstone WADO Image Loader for robust DICOM file handling.
## Features
- ✅ WebView-based DICOM rendering
- ✅ Cornerstone.js integration for medical imaging
- ✅ Support for remote DICOM URLs
- ✅ Loading states and error handling
- ✅ Real-time communication with React Native
- ✅ Responsive design for mobile devices
## Components
### 1. DicomViewer
The main DICOM viewer component.
**Props:**
```typescript
interface DicomViewerProps {
dicomUrl: string; // URL to the DICOM file
onError?: (error: string) => void; // Error callback
onLoad?: () => void; // Load success callback
}
```
**Usage:**
```typescript
import { DicomViewer } from '../shared/components';
<DicomViewer
dicomUrl="https://example.com/sample.dcm"
onError={(error) => console.error('DICOM Error:', error)}
onLoad={() => console.log('DICOM loaded successfully')}
/>
```
### 2. DicomViewerTest
A test component for testing different DICOM URLs and debugging issues.
**Usage:**
```typescript
import { DicomViewerTest } from '../shared/components';
<DicomViewerTest />
```
## How It Works
### 1. WebView Setup
- Loads a local HTML file (`dicom-viewer.html`)
- Enables JavaScript and DOM storage
- Allows file access and universal access from file URLs
### 2. Library Loading
- Dynamically loads Cornerstone.js from CDN
- Loads Cornerstone WADO Image Loader
- Initializes the viewer when libraries are ready
### 3. DICOM Processing
- Receives DICOM URL from React Native via postMessage
- Uses Cornerstone to load and display the DICOM image
- Handles errors and success states
### 4. Communication
- Sends status messages back to React Native
- Reports loading, success, and error states
- Enables debugging and user feedback
## Troubleshooting
### Black Screen Issues
#### 1. Check Console Logs
Open the React Native debugger and check for:
- WebView loading errors
- JavaScript execution errors
- Network request failures
#### 2. Verify DICOM URL
- Ensure the URL is accessible from the device
- Check if the URL returns a valid DICOM file
- Verify CORS settings if loading from a different domain
#### 3. Library Loading Issues
- Check internet connectivity (libraries load from CDN)
- Verify the HTML file path is correct
- Check WebView permissions and settings
#### 4. Platform-Specific Issues
**Android:**
- Ensure `allowFileAccess` is enabled
- Check if the HTML file is in the correct assets folder
- Verify WebView permissions in AndroidManifest.xml
**iOS:**
- Check WebView configuration in Info.plist
- Ensure JavaScript is enabled
- Verify file access permissions
### Common Error Messages
#### "Failed to load DICOM viewer libraries"
- Check internet connectivity
- Verify CDN URLs are accessible
- Check WebView JavaScript settings
#### "Failed to load DICOM image"
- Verify DICOM URL is accessible
- Check if the file is a valid DICOM format
- Ensure the server supports CORS
#### "Invalid message received from app"
- Check the message format being sent
- Verify the postMessage implementation
- Check WebView message handling
## Testing
### 1. Use Sample URLs
The test component includes sample DICOM URLs that are known to work:
- Sample DICOM 1-3 from OHIF examples
### 2. Test Custom URLs
- Enter your own DICOM URLs
- Test with different file formats
- Verify error handling
### 3. Debug Mode
- Check console logs in React Native debugger
- Monitor WebView messages
- Use the test component for isolated testing
## Performance Tips
### 1. Image Optimization
- Use compressed DICOM files when possible
- Consider implementing progressive loading
- Cache frequently accessed images
### 2. Memory Management
- Dispose of WebView when not in use
- Monitor memory usage with large DICOM files
- Implement proper cleanup in useEffect
### 3. Network Optimization
- Use CDN for DICOM files when possible
- Implement retry logic for failed requests
- Consider offline caching for critical images
## Security Considerations
### 1. URL Validation
- Validate DICOM URLs before loading
- Implement URL whitelisting if needed
- Sanitize user input for custom URLs
### 2. WebView Security
- Limit WebView permissions to minimum required
- Implement proper origin whitelisting
- Monitor for malicious content
### 3. Data Privacy
- Ensure DICOM files don't contain PHI
- Implement proper data handling protocols
- Follow HIPAA compliance guidelines
## Future Enhancements
### 1. Offline Support
- Bundle Cornerstone libraries locally
- Implement offline DICOM caching
- Support for local DICOM files
### 2. Advanced Features
- Multi-planar reconstruction (MPR)
- Measurement tools
- Annotation capabilities
- 3D rendering support
### 3. Performance Improvements
- WebAssembly integration
- GPU acceleration
- Progressive image loading
- Background processing
## Support
For issues and questions:
1. Check this README for common solutions
2. Review console logs and error messages
3. Test with sample URLs first
4. Verify WebView configuration
5. Check platform-specific requirements
## Dependencies
- `react-native-webview`: WebView component
- `cornerstone-core`: Medical imaging library
- `cornerstone-wado-image-loader`: DICOM file loader
## License
Design & Developed by Tech4Biz Solutions
Copyright (c) Spurrin Innovations. All rights reserved.

View File

@ -0,0 +1,306 @@
/*
* File: DicomViewer.tsx
* Description: DICOM viewer component using WebView for medical imaging
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useRef, useEffect, useState } from 'react';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { Platform, View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
// Interface for component props
interface DicomViewerProps {
dicomUrl: string;
onError?: (error: string) => void;
onLoad?: () => void;
debugMode?: boolean;
}
// Interface for WebView reference
interface WebViewRef {
postMessage: (message: string) => void;
reload: () => void;
}
export default function DicomViewer({ dicomUrl, onError, onLoad, debugMode = false }: DicomViewerProps): React.ReactElement {
const webViewRef = useRef<WebViewRef>(null);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [debugInfo, setDebugInfo] = useState<string[]>([]);
const [webViewReady, setWebViewReady] = useState(false);
// Debug logging function
const debugLog = (message: string) => {
if (debugMode) {
const timestamp = new Date().toLocaleTimeString();
const logMessage = `[${timestamp}] ${message}`;
console.log(logMessage);
setDebugInfo(prev => [...prev.slice(-9), logMessage]); // Keep last 10 messages
}
};
// Handle WebView load events
const handleLoadStart = () => {
debugLog('WebView load started');
setIsLoading(true);
setHasError(false);
};
const handleLoadEnd = () => {
debugLog('WebView load ended');
setIsLoading(false);
setWebViewReady(true);
onLoad?.();
};
const handleError = (error: any) => {
debugLog(`WebView error: ${JSON.stringify(error)}`);
setIsLoading(false);
setHasError(true);
onError?.(error?.nativeEvent?.description || 'Failed to load DICOM viewer');
};
const handleMessage = (event: WebViewMessageEvent) => {
try {
const message = event.nativeEvent.data;
debugLog(`Message from WebView: ${message}`);
// Try to parse JSON message
if (typeof message === 'string') {
try {
const parsedMessage = JSON.parse(message);
debugLog(`Parsed message: ${JSON.stringify(parsedMessage)}`);
if (parsedMessage.type === 'error') {
setHasError(true);
onError?.(parsedMessage.message);
} else if (parsedMessage.type === 'success') {
setHasError(false);
}
} catch (parseError) {
debugLog(`Failed to parse message as JSON: ${parseError}`);
}
}
} catch (error) {
debugLog(`Error handling WebView message: ${error}`);
}
};
// Send DICOM URL to WebView when component mounts or URL changes
useEffect(() => {
if (webViewRef.current && dicomUrl && webViewReady) {
debugLog(`Sending DICOM URL to WebView: ${dicomUrl}`);
// Wait a bit for WebView to be ready
const timer = setTimeout(() => {
if (webViewRef.current) {
try {
webViewRef.current.postMessage(dicomUrl);
debugLog('DICOM URL sent successfully');
} catch (error) {
debugLog(`Failed to send DICOM URL: ${error}`);
}
}
}, 1000);
return () => clearTimeout(timer);
}
}, [dicomUrl, webViewReady]);
// Reload WebView if there's an error
const handleRetry = () => {
debugLog('Retrying WebView load');
if (webViewRef.current) {
setHasError(false);
setIsLoading(true);
setWebViewReady(false);
webViewRef.current.reload();
}
};
// Clear debug info
const clearDebugInfo = () => {
setDebugInfo([]);
};
return (
<View style={styles.container}>
<WebView
ref={webViewRef as any}
source={require('../../assets/dicom/dicom-viewer.html')}
originWhitelist={['*']}
javaScriptEnabled
domStorageEnabled
allowFileAccess
allowUniversalAccessFromFileURLs
allowFileAccessFromFileURLs
onLoadStart={handleLoadStart}
onLoadEnd={handleLoadEnd}
onError={handleError}
onMessage={handleMessage}
style={styles.webview}
startInLoadingState
renderLoading={() => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>Loading DICOM Viewer...</Text>
</View>
)}
/>
{hasError && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Failed to load DICOM viewer</Text>
<Text style={styles.errorDetails}>
URL: {dicomUrl}
</Text>
<TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
<Text style={styles.retryButtonText}>Tap to retry</Text>
</TouchableOpacity>
</View>
)}
{debugMode && (
<View style={styles.debugContainer}>
<View style={styles.debugHeader}>
<Text style={styles.debugTitle}>Debug Info</Text>
<TouchableOpacity onPress={clearDebugInfo}>
<Text style={styles.clearButton}>Clear</Text>
</TouchableOpacity>
</View>
<View style={styles.debugContent}>
{debugInfo.map((info, index) => (
<Text key={index} style={styles.debugText}>{info}</Text>
))}
</View>
<View style={styles.debugStatus}>
<Text style={styles.debugStatusText}>
WebView Ready: {webViewReady ? 'Yes' : 'No'}
</Text>
<Text style={styles.debugStatusText}>
Loading: {isLoading ? 'Yes' : 'No'}
</Text>
<Text style={styles.debugStatusText}>
Error: {hasError ? 'Yes' : 'No'}
</Text>
</View>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
webview: {
flex: 1,
backgroundColor: '#000',
},
loadingContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
loadingText: {
color: '#FFF',
marginTop: 16,
fontSize: 16,
},
errorContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
padding: 20,
},
errorText: {
color: '#F44336',
fontSize: 18,
textAlign: 'center',
marginBottom: 16,
fontWeight: '600',
},
errorDetails: {
color: '#FF9800',
fontSize: 14,
textAlign: 'center',
marginBottom: 20,
fontFamily: 'monospace',
},
retryButton: {
backgroundColor: '#2196F3',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
},
retryButtonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
debugContainer: {
position: 'absolute',
top: 10,
right: 10,
backgroundColor: 'rgba(0,0,0,0.9)',
borderRadius: 8,
padding: 10,
maxWidth: 300,
maxHeight: 400,
},
debugHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
debugTitle: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
},
clearButton: {
color: '#2196F3',
fontSize: 12,
textDecorationLine: 'underline',
},
debugContent: {
maxHeight: 200,
},
debugText: {
color: '#FFFFFF',
fontSize: 10,
fontFamily: 'monospace',
marginBottom: 2,
},
debugStatus: {
marginTop: 8,
paddingTop: 8,
borderTopColor: '#333',
borderTopWidth: 1,
},
debugStatusText: {
color: '#CCC',
fontSize: 10,
marginBottom: 2,
},
});
/*
* End of File: DicomViewer.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,252 @@
/*
* File: DicomViewerTest.tsx
* Description: Test component for DICOM viewer with sample URLs
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, Alert } from 'react-native';
import DicomViewer from './DicomViewer';
// Sample DICOM URLs for testing
const SAMPLE_DICOM_URLS = [
{
name: 'Sample DICOM 1',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'
},
{
name: 'Sample DICOM 2',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm'
},
{
name: 'Sample DICOM 3',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm'
}
];
export default function DicomViewerTest(): React.ReactElement {
const [dicomUrl, setDicomUrl] = useState(SAMPLE_DICOM_URLS[0].url);
const [customUrl, setCustomUrl] = useState('');
const handleUrlSelect = (url: string) => {
setDicomUrl(url);
setCustomUrl(url);
};
const handleCustomUrlSubmit = () => {
if (customUrl.trim()) {
setDicomUrl(customUrl.trim());
} else {
Alert.alert('Error', 'Please enter a valid URL');
}
};
const handleViewerError = (error: string) => {
console.error('DICOM Viewer Error:', error);
Alert.alert('DICOM Viewer Error', error);
};
const handleViewerLoad = () => {
console.log('DICOM Viewer loaded successfully');
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>DICOM Viewer Test</Text>
<Text style={styles.subtitle}>Test different DICOM URLs</Text>
</View>
<ScrollView style={styles.urlSection}>
<Text style={styles.sectionTitle}>Sample DICOM URLs:</Text>
{SAMPLE_DICOM_URLS.map((sample, index) => (
<TouchableOpacity
key={index}
style={[
styles.urlButton,
dicomUrl === sample.url && styles.selectedUrlButton
]}
onPress={() => handleUrlSelect(sample.url)}
>
<Text style={[
styles.urlButtonText,
dicomUrl === sample.url && styles.selectedUrlButtonText
]}>
{sample.name}
</Text>
<Text style={[
styles.urlText,
dicomUrl === sample.url && styles.selectedUrlText
]}>
{sample.url}
</Text>
</TouchableOpacity>
))}
<View style={styles.customUrlSection}>
<Text style={styles.sectionTitle}>Custom DICOM URL:</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={customUrl}
onChangeText={setCustomUrl}
placeholder="Enter DICOM URL..."
placeholderTextColor="#999"
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity
style={styles.submitButton}
onPress={handleCustomUrlSubmit}
>
<Text style={styles.submitButtonText}>Load</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.currentUrlSection}>
<Text style={styles.sectionTitle}>Current URL:</Text>
<Text style={styles.currentUrl}>{dicomUrl}</Text>
</View>
</ScrollView>
<View style={styles.viewerContainer}>
<Text style={styles.viewerTitle}>DICOM Viewer:</Text>
<DicomViewer
dicomUrl={dicomUrl}
onError={handleViewerError}
onLoad={handleViewerLoad}
debugMode={true}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: '#2196F3',
padding: 20,
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#E3F2FD',
},
urlSection: {
flex: 1,
padding: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#212121',
marginBottom: 12,
marginTop: 16,
},
urlButton: {
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
marginBottom: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
selectedUrlButton: {
backgroundColor: '#E3F2FD',
borderColor: '#2196F3',
},
urlButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#212121',
marginBottom: 4,
},
selectedUrlButtonText: {
color: '#1976D2',
},
urlText: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
},
selectedUrlText: {
color: '#1976D2',
},
customUrlSection: {
marginTop: 16,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
},
textInput: {
flex: 1,
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E0E0E0',
borderRadius: 8,
padding: 12,
fontSize: 14,
color: '#212121',
marginRight: 8,
},
submitButton: {
backgroundColor: '#4CAF50',
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 8,
},
submitButtonText: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
},
currentUrlSection: {
marginTop: 16,
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
currentUrl: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
backgroundColor: '#F5F5F5',
padding: 8,
borderRadius: 4,
},
viewerContainer: {
flex: 2,
backgroundColor: '#000000',
margin: 16,
borderRadius: 8,
overflow: 'hidden',
},
viewerTitle: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
padding: 12,
backgroundColor: '#333333',
},
});
/*
* End of File: DicomViewerTest.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -15,6 +15,12 @@ export { CustomModal } from './CustomModal';
// Coming Soon Screen Component
export { ComingSoonScreen } from './ComingSoonScreen';
// DICOM Viewer Component
export { default as DicomViewer } from './DicomViewer';
// DICOM Viewer Test Component
export { default as DicomViewerTest } from './DicomViewerTest';
// ============================================================================
// TYPE EXPORTS
// ============================================================================

15
package-lock.json generated
View File

@ -44,6 +44,7 @@
"react-native-toast-message": "^2.2.1",
"react-native-tts": "^4.1.1",
"react-native-vector-icons": "^10.2.0",
"react-native-webview": "^13.15.0",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0"
},
@ -12077,6 +12078,20 @@
"node": ">=10"
}
},
"node_modules/react-native-webview": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
"integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz",

View File

@ -46,6 +46,7 @@
"react-native-toast-message": "^2.2.1",
"react-native-tts": "^4.1.1",
"react-native-vector-icons": "^10.2.0",
"react-native-webview": "^13.15.0",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0"
},