// SpeedTestActivity.kt // SpeedOf.Me API - Android/Kotlin Integration // // This file demonstrates how to integrate the SpeedOf.Me speed test // into an Android app using WebView. package com.example.speedtest import android.annotation.SuppressLint import android.os.Bundle import android.util.Log import android.webkit.JavascriptInterface import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import org.json.JSONObject // MARK: - Data Classes data class SpeedTestResult( val download: Double, val upload: Double, val latency: Double, val jitter: Double, val testServer: String? = null, val ipAddress: String? = null, val hostname: String? = null ) data class SpeedTestProgress( val testType: String, val currentSpeed: Double, val percentComplete: Double, val passNumber: Int ) data class SpeedTestError( val code: Int, val message: String ) sealed class SpeedTestState { object Idle : SpeedTestState() object Running : SpeedTestState() data class Progress(val progress: SpeedTestProgress) : SpeedTestState() data class Completed(val result: SpeedTestResult) : SpeedTestState() data class Error(val error: SpeedTestError) : SpeedTestState() } // MARK: - ViewModel class SpeedTestViewModel { private val _state = MutableStateFlow(SpeedTestState.Idle) val state: StateFlow = _state private val _statusText = MutableStateFlow("Ready") val statusText: StateFlow = _statusText fun handleMessage(json: String) { try { val message = JSONObject(json) val type = message.getString("type") val data = message.optJSONObject("data") ?: JSONObject() when (type) { "ready" -> { _statusText.value = "Ready to test" _state.value = SpeedTestState.Idle } "started" -> { _statusText.value = "Starting test..." _state.value = SpeedTestState.Running } "progress" -> { val progress = SpeedTestProgress( testType = data.optString("testType", "download"), currentSpeed = data.optDouble("currentSpeed", 0.0), percentComplete = data.optDouble("percentComplete", 0.0), passNumber = data.optInt("passNumber", 1) ) val testType = if (progress.testType == "download") "Download" else "Upload" _statusText.value = "$testType: ${String.format("%.1f", progress.currentSpeed)} Mbps" _state.value = SpeedTestState.Progress(progress) } "completed" -> { val result = SpeedTestResult( download = data.optDouble("download", 0.0), upload = data.optDouble("upload", 0.0), latency = data.optDouble("latency", 0.0), jitter = data.optDouble("jitter", 0.0), testServer = data.optString("testServer", null), ipAddress = data.optString("ip_address", null), hostname = data.optString("hostname", null) ) _statusText.value = "Test complete" _state.value = SpeedTestState.Completed(result) } "error" -> { val error = SpeedTestError( code = data.optInt("code", 0), message = data.optString("message", "Unknown error") ) _statusText.value = "Error: ${error.message}" _state.value = SpeedTestState.Error(error) } } } catch (e: Exception) { Log.e("SpeedTest", "Error parsing message: ${e.message}") } } } // MARK: - JavaScript Interface class SpeedTestJsInterface(private val viewModel: SpeedTestViewModel) { @JavascriptInterface fun onMessage(json: String) { viewModel.handleMessage(json) } } // MARK: - Activity class SpeedTestActivity : ComponentActivity() { private val viewModel = SpeedTestViewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { SpeedTestScreen(viewModel) } } } } // MARK: - Composable UI @Composable fun SpeedTestScreen(viewModel: SpeedTestViewModel) { val state by viewModel.state.collectAsState() val statusText by viewModel.statusText.collectAsState() Column( modifier = Modifier.fillMaxSize() ) { // Status bar Surface( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface, tonalElevation = 2.dp ) { Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = statusText, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f) ) if (state is SpeedTestState.Running || state is SpeedTestState.Progress) { CircularProgressIndicator( modifier = Modifier.size(24.dp), strokeWidth = 2.dp ) } } } // WebView SpeedTestWebView( viewModel = viewModel, modifier = Modifier.weight(1f) ) // Native results display (optional) if (state is SpeedTestState.Completed) { val result = (state as SpeedTestState.Completed).result Surface( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colorScheme.surface, tonalElevation = 2.dp ) { Column(modifier = Modifier.padding(16.dp)) { ResultRow("Download", "${result.download} Mbps") ResultRow("Upload", "${result.upload} Mbps") ResultRow("Latency", "${result.latency} ms") } } } } } @Composable fun ResultRow(label: String, value: String) { Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Text( text = label, color = MaterialTheme.colorScheme.onSurfaceVariant ) Text( text = value, color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.bodyLarge ) } } @SuppressLint("SetJavaScriptEnabled") @Composable fun SpeedTestWebView( viewModel: SpeedTestViewModel, modifier: Modifier = Modifier ) { AndroidView( modifier = modifier.fillMaxWidth(), factory = { context -> WebView(context).apply { settings.apply { javaScriptEnabled = true domStorageEnabled = true mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE } webViewClient = WebViewClient() // Add JavaScript interface addJavascriptInterface( SpeedTestJsInterface(viewModel), "Android" ) // Load the speed test HTML // Option 1: Load from assets loadUrl("file:///android_asset/speedtest.html") // Option 2: Load from remote server // loadUrl("https://your-domain.com/speedtest.html") } } ) }