// SpeedTestScreen.tsx // SpeedOf.Me API - React Native Integration // // This file demonstrates how to integrate the SpeedOf.Me speed test // into a React Native app using react-native-webview. import React, { useState, useRef, useCallback } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, SafeAreaView, } from 'react-native'; import { WebView, WebViewMessageEvent } from 'react-native-webview'; // MARK: - Types interface SpeedTestResult { download: number; upload: number; latency: number; jitter: number; testServer?: string; ip_address?: string; hostname?: string; } interface SpeedTestProgress { testType: 'download' | 'upload'; currentSpeed: number; percentComplete: number; passNumber: number; } interface SpeedTestError { code: number; message: string; } interface WebViewMessage { type: 'ready' | 'started' | 'progress' | 'completed' | 'error'; data: any; } type TestStatus = 'idle' | 'running' | 'completed' | 'error'; // MARK: - Component export default function SpeedTestScreen() { const webViewRef = useRef(null); const [status, setStatus] = useState('idle'); const [statusText, setStatusText] = useState('Ready'); const [result, setResult] = useState(null); const [progress, setProgress] = useState(null); const [error, setError] = useState(null); // Handle messages from WebView const handleMessage = useCallback((event: WebViewMessageEvent) => { try { const message: WebViewMessage = JSON.parse(event.nativeEvent.data); switch (message.type) { case 'ready': setStatusText('Ready to test'); setStatus('idle'); break; case 'started': setStatusText('Starting test...'); setStatus('running'); setResult(null); setError(null); break; case 'progress': const prog = message.data as SpeedTestProgress; setProgress(prog); const testType = prog.testType === 'download' ? 'Download' : 'Upload'; setStatusText(`${testType}: ${prog.currentSpeed.toFixed(1)} Mbps`); break; case 'completed': const res = message.data as SpeedTestResult; setResult(res); setProgress(null); setStatus('completed'); setStatusText('Test complete'); break; case 'error': const err = message.data as SpeedTestError; setError(err); setStatus('error'); setStatusText(`Error: ${err.message}`); break; } } catch (e) { console.error('Error parsing WebView message:', e); } }, []); // HTML source options // Option 1: Remote URL (domain must match SpeedOf.Me registration) const webViewSource = { uri: 'https://your-domain.com/speedtest.html' }; // Option 2: Inline HTML (for bundling with app) // const webViewSource = { html: SPEED_TEST_HTML }; // Option 3: Local file (requires setup) // const webViewSource = { uri: 'file:///android_asset/speedtest.html' }; return ( {/* Status Bar */} {statusText} {(status === 'running') && ( )} {/* Progress Bar */} {progress && ( Pass {progress.passNumber} - {progress.percentComplete.toFixed(0)}% )} {/* WebView */} {/* Native Results Display (optional) */} {result && ( )} {/* Error Display */} {error && ( Error {error.code}: {error.message} )} ); } // MARK: - Result Row Component interface ResultRowProps { label: string; value: string; } function ResultRow({ label, value }: ResultRowProps) { return ( {label} {value} ); } // MARK: - Styles const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f5f5f5', }, statusBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', padding: 16, backgroundColor: 'white', borderBottomWidth: 1, borderBottomColor: '#eee', }, statusText: { fontSize: 17, fontWeight: '600', color: '#333', }, progressContainer: { padding: 16, backgroundColor: 'white', }, progressBar: { height: 4, backgroundColor: '#e0e0e0', borderRadius: 2, overflow: 'hidden', }, progressFill: { height: '100%', backgroundColor: '#007AFF', }, progressText: { marginTop: 8, fontSize: 13, color: '#666', textAlign: 'center', }, webView: { flex: 1, }, resultsContainer: { backgroundColor: 'white', padding: 16, borderTopWidth: 1, borderTopColor: '#eee', }, resultRow: { flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 8, }, resultLabel: { fontSize: 15, color: '#666', }, resultValue: { fontSize: 15, fontWeight: '600', color: '#007AFF', }, errorContainer: { padding: 16, backgroundColor: '#ffebee', }, errorText: { color: '#c62828', fontSize: 14, }, }); // MARK: - Inline HTML (Alternative to remote URL) // Uncomment and use this if you want to bundle the HTML with your app /* const SPEED_TEST_HTML = `
`; */