Internet Speed Test app Ui made using Jetpack Compose
Yes, it is definitely possible to create an Internet Speed Test UI app using Jetpack Compose. Jetpack Compose is a modern toolkit for building native Android user interfaces using a declarative approach. It is a powerful and flexible tool that allows developers to create beautiful, responsive UIs with less code.
To create an Internet Speed Test UI app using Jetpack Compose, you would first need to define the layout of your app using Compose UI components. You could create a simple layout with a start button to initiate the speed test, a progress indicator to show the progress of the test, and a result display to show the download and upload speeds.
Next, you would need to implement the speed test logic. You could use a third-party library or API to perform the speed test and get the download and upload speeds. Once you have the results, you can update the UI to display the speeds and any other relevant information.
Overall, Jetpack Compose is a great choice for building UIs, and it can be used to create a wide range of apps, including an Internet Speed Test UI app.
Internet Speed Test app Example source code
lets create a new android project using with jetpack compose activity. follow the below steps.
Add below dependency
dependencies { implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' implementation 'androidx.activity:activity-compose:1.5.1' implementation "androidx.compose.ui:ui:$compose_ui_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version" implementation 'androidx.compose.material:material:1.2.1' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version" //Internet speed checker implementation 'com.github.oatrice:internet-speed-testing:1.0.1' }
MainActivity.kt Source code Internet Speed Test
import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface import androidx.compose.ui.Modifier import com.laboontech.internetspeedtestui.presentation.feature_speedtest.SpeedTestScreen import com.laboontech.internetspeedtestui.presentation.ui.theme.InternetSpeedTesterTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { InternetSpeedTesterTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background ) { SpeedTestScreen() } } } } }
Step 2: Create a Utils folder and create kotlin file BottomMenuContent.
import androidx.annotation.DrawableRes data class BottomMenuContent( val title: String, @DrawableRes val iconId: Int )
Step 3 : Create a Constants kotlin file in Utils. Internet Speed Test
object Constants { const val DEFAULT_NUMBER_OF_LINES = 40 const val MAX_VALUE_FOR_LINES = 240f const val TAG_TEST = "TestMyAndroidApp" }
Step 4: Create a kotlin file with name SpeedTestScreen
in this kotlin file we are design the all UI and function of the main screen.
import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.LinearOutSlowInEasing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.keyframes import androidx.compose.animation.core.spring import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.BottomNavigation import androidx.compose.material.ButtonDefaults import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Devices import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.laboontech.internetspeedtestui.R import com.laboontech.internetspeedtestui.presentation.ui.theme.ArcColorPrimary import com.laboontech.internetspeedtestui.presentation.ui.theme.DarkGradient import com.laboontech.internetspeedtestui.presentation.ui.theme.LightColor import com.laboontech.internetspeedtestui.presentation.ui.theme.ArcGradient import com.laboontech.internetspeedtestui.presentation.ui.theme.DarkColor import com.laboontech.internetspeedtestui.presentation.ui.theme.DarkColor2 import com.laboontech.internetspeedtestui.presentation.ui.theme.LightColor2 import com.laboontech.internetspeedtestui.presentation.ui.theme.TitleColor import com.laboontech.internetspeedtestui.presentation.uistate.UiState import com.laboontech.internetspeedtestui.utils.BottomMenuContent import com.laboontech.internetspeedtestui.utils.Constants import kotlinx.coroutines.launch import kotlin.math.floor import kotlin.math.roundToInt /** * SpeedTest Main Screen * */ @Composable fun SpeedTestScreen() { val coroutineScope = rememberCoroutineScope() val animation = remember { Animatable(0f) } val maxSpeed = remember { mutableStateOf(0f) } maxSpeed.value = java.lang.Float.max(maxSpeed.value, animation.value * 100f) Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier .fillMaxSize() .background(DarkGradient) ) { Header() Spacer(modifier = Modifier.height(30.dp)) SpeedIndicatorContent(state = animation.toUiState(maxSpeed.value)) { coroutineScope.launch { maxSpeed.value = 0f startAnimation(animation) } } Spacer(modifier = Modifier.height(40.dp)) SpeedInformation(state = animation.toUiState(maxSpeed.value)) } BottomMenu( items = listOf( BottomMenuContent( stringResource(id = R.string.network), R.drawable.baseline_signal_wifi_statusbar_connected_no_internet_4_24 ), BottomMenuContent( stringResource(id = R.string.speed), R.drawable.baseline_speed_24 ), BottomMenuContent( stringResource(id = R.string.location), R.drawable.baseline_person_pin_circle_24 ) ), modifier = Modifier.align(Alignment.BottomCenter) ) } } suspend fun startAnimation(animation: Animatable<Float, AnimationVector1D>) { animation.animateTo(0.65f, keyframes { durationMillis = 9000 0f at 0 with CubicBezierEasing(0f, 1.5f, 0.8f, 1f) 0.12f at 1000 with CubicBezierEasing(0.2f, -1.5f, 0f, 1f) 0.20f at 2000 with CubicBezierEasing(0.2f, -2f, 0f, 1f) 0.35f at 3000 with CubicBezierEasing(0.2f, -1.5f, 0f, 1f) 0.62f at 4000 with CubicBezierEasing(0.2f, -2f, 0f, 1f) 0.75f at 5000 with CubicBezierEasing(0.2f, -2f, 0f, 1f) 0.89f at 6000 with CubicBezierEasing(0.2f, -1.2f, 0f, 1f) 0.82f at 7500 with LinearOutSlowInEasing }) } fun Animatable<Float, AnimationVector1D>.toUiState(maxSpeed: Float) = UiState( arcValue = value, speed = "%.1f".format(value * 100), uploadSpeed = "%.1f".format(value * 50), ping = if (value > 0.2f) "${(value * 15).roundToInt()} ms" else "0.0 ms", maxSpeed = if (maxSpeed > 0f) "%.1f mbps".format(maxSpeed) else "0.0 mbps", inProgress = isRunning ) /** * Header View - Header Title and Setting Icon * */ @Composable fun Header() { Row( modifier = Modifier .fillMaxWidth() .padding(20.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( text = stringResource(id = R.string.app_name), color = Color.White, fontSize = 24.sp, fontFamily = FontFamily.Serif ) Image( painter = painterResource(id = R.drawable.settings), contentDescription = stringResource(id = R.string.settings) ) } } /** * Download Speed, Upload Speed, Max Speed and Ping Information * */ @Composable fun SpeedInformation( state: UiState ) { @Composable fun RowScope.InfoColumn(title: String, value: String) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.weight(1f) ) { Text( text = title, color = TitleColor, fontFamily = FontFamily.Monospace, fontSize = 12.sp ) Text( text = value, style = MaterialTheme.typography.body2, color = Color.White, modifier = Modifier.padding(vertical = 8.dp), fontSize = 16.sp, fontWeight = FontWeight.Bold ) } } // Download Speed // Upload Speed Row( Modifier .fillMaxWidth() .height(IntrinsicSize.Min) ) { InfoColumn( title = stringResource(id = R.string.download_speed), value = "${state.speed} mbps" ) VerticalDivider() InfoColumn( title = stringResource(id = R.string.upload_speed), value = "${state.uploadSpeed} mbps" ) } Spacer(modifier = Modifier.height(40.dp)) // Ping // Max Speed Row( Modifier .fillMaxWidth() .height(IntrinsicSize.Min) ) { InfoColumn(title = stringResource(id = R.string.ping), value = state.ping) VerticalDivider() InfoColumn( title = stringResource(id = R.string.max_speed), value = state.maxSpeed ) } } /** * Vertical Divider * */ @Composable fun VerticalDivider() { Box( modifier = Modifier .fillMaxHeight() .background(Color(0xFF414D66)) .width(1.dp) ) } /** * This function contains - Speed Indicator, Speed Value text, Start/Stop button * */ @Composable fun SpeedIndicatorContent( state: UiState, onclick: () -> Unit ) { val buttonHorizontalPadding by animateDpAsState( targetValue = if (state.inProgress) 40.dp else 20.dp, animationSpec = spring( dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow ) ) Box( contentAlignment = Alignment.BottomCenter, modifier = Modifier .fillMaxWidth() .aspectRatio(1f) ) { /* Circular speed indicator */ Canvas( modifier = Modifier .fillMaxSize() .padding(40.dp) ) { // Draw Scale lines drawLines(state.arcValue, Constants.MAX_VALUE_FOR_LINES) // Draw Arc drawArcs(state.arcValue, Constants.MAX_VALUE_FOR_LINES) } /* Speed value Texts */ Column( modifier = Modifier .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // Download Text Text( text = stringResource(id = R.string.download), style = MaterialTheme.typography.caption, color = Color.White ) // Speed Value Text( text = state.speed, fontSize = 45.sp, color = Color.White, fontWeight = FontWeight.Bold, fontFamily = FontFamily.SansSerif ) // mbps text Text( text = stringResource(id = R.string.mbps), style = MaterialTheme.typography.caption, color = Color.White ) } /* Start check speed value Button */ OutlinedButton( onClick = { if (!state.inProgress) onclick() }, modifier = Modifier.padding(bottom = 24.dp), colors = ButtonDefaults.buttonColors( backgroundColor = Color.Transparent, contentColor = Color.White ), shape = RoundedCornerShape(24.dp), border = BorderStroke(width = 1.dp, color = LightColor2), ) { // Start button text Text( modifier = Modifier .padding(horizontal = buttonHorizontalPadding, vertical = 4.dp), text = if (!state.inProgress) { stringResource(id = R.string.start) } else { stringResource(id = R.string.checking_internet_speed) }, color = if (state.inProgress) { Color.White.copy(alpha = 0.5f) } else { Color.White } ) } } } /** * Draw arcs extension function to draw an speed indicator arc above the lines * */ fun DrawScope.drawArcs(progress: Float, maxValue: Float) { val startAngle = 270 - maxValue / 2 val sweepAngle = maxValue * progress val topLeft = Offset(50f, 50f) val size = Size(size.width - 100f, size.height - 100f) fun drawBlur() { for (i in 0..20) { drawArc( color = ArcColorPrimary.copy(alpha = i / 900f), startAngle = startAngle, sweepAngle = sweepAngle, useCenter = false, topLeft = topLeft, size = size, style = Stroke(width = 80f + (20 - i) * 20, cap = StrokeCap.Round) ) } } fun drawStroke() { drawArc( color = ArcColorPrimary, startAngle = startAngle, sweepAngle = sweepAngle, useCenter = false, topLeft = topLeft, size = size, style = Stroke(width = 86f, cap = StrokeCap.Round) ) } fun drawGradient() { drawArc( brush = ArcGradient, startAngle = startAngle, sweepAngle = sweepAngle, useCenter = false, topLeft = topLeft, size = size, style = Stroke(width = 80f, cap = StrokeCap.Round) ) } drawBlur() drawStroke() drawGradient() } /** * Draw lines extension function to draw lines which look like a round scale * */ fun DrawScope.drawLines( progress: Float, maxValue: Float, numberOfLines: Int = Constants.DEFAULT_NUMBER_OF_LINES ) { // To calculate rotation size val oneRotation = maxValue / numberOfLines // Start value of indicator, it can start from 0 or from current progress val startValue = if (progress == 0f) 0 else floor(x = progress * numberOfLines).toInt() + 1 // Loop starts from start value to the number of lines for (i in startValue..numberOfLines) { val rotationDegree = i * oneRotation + (180 - maxValue) / 2 rotate(degrees = rotationDegree) { drawLine( color = LightColor, start = Offset(if (i % 5 == 0) 80f else 30f, size.height / 2), end = Offset(0f, size.height / 2), strokeWidth = 8f, cap = StrokeCap.Round ) } } } @Composable fun BottomMenu( items: List<BottomMenuContent>, modifier: Modifier = Modifier, activeHighlightColor: Color = LightColor, activeTextColor: Color = Color.White, inactiveTextColor: Color = LightColor2, initialSelectedItemIndex: Int = 1 ) { var selectedItemIndex by remember { mutableStateOf(initialSelectedItemIndex) } Row( horizontalArrangement = Arrangement.SpaceAround, verticalAlignment = Alignment.CenterVertically, modifier = modifier .fillMaxWidth() .background(DarkColor) .padding(15.dp) ) { items.forEachIndexed { index, item -> BottomMenuItem( item = item, isSelected = index == selectedItemIndex, activeHighlightColor = activeHighlightColor, activeTextColor = activeTextColor, inactiveTextColor = inactiveTextColor ) { selectedItemIndex = index } } } } @Composable fun BottomMenuItem( item: BottomMenuContent, isSelected: Boolean = false, activeHighlightColor: Color = LightColor, activeTextColor: Color = Color.White, inactiveTextColor: Color = LightColor2, onItemClick: () -> Unit ) { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = Modifier .clickable { onItemClick() } ) { Box( contentAlignment = Alignment.Center, modifier = Modifier .clip(RoundedCornerShape(10.dp)) .background(if (isSelected) activeHighlightColor else Color.Transparent) .padding(10.dp) ) { Icon( painter = painterResource(id = item.iconId), contentDescription = item.title, tint = if (isSelected) activeTextColor else inactiveTextColor, modifier = Modifier.size(20.dp) ) } Text( text = item.title, color = if (isSelected) activeTextColor else inactiveTextColor ) } } @Preview(device = Devices.PIXEL) @Composable fun SpeedTestScreenPreview() { SpeedTestScreen() }
Create a File name State
class UiState( val speed: String = "", val uploadSpeed: String = "", val ping: String = "-", val maxSpeed: String = "-", val arcValue: Float = 0f, val inProgress: Boolean = false )
Internet Speed Test Full Souce code on Github
Read More Tutorial
- Codeplayon Jetpack Compose Tutorial
- Codeplayon Android Tutorial
- Codeplayon Flutter Tutorial
- Codeplayon on Github