Coverage Summary for Class: HomeScreenKt (debug.com.greybox.projectmesh.views)
| Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| HomeScreenKt$ConcurrencyWarningDialog$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$ConcurrencyWarningDialog$2 |
0%
(0/1)
|
|
| HomeScreenKt$ConcurrencyWarningDialog$2$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$ConcurrencyWarningDialog$3 |
|
| HomeScreenKt$HomeScreen$$inlined$instance$default$1 |
0%
(0/1)
|
|
| HomeScreenKt$HomeScreen$$inlined$instance$default$2 |
0%
(0/1)
|
|
| HomeScreenKt$HomeScreen$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/5)
|
| HomeScreenKt$HomeScreen$2 |
0%
(0/1)
|
0%
(0/8)
|
0%
(0/8)
|
0%
(0/39)
|
| HomeScreenKt$HomeScreen$6 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/3)
|
0%
(0/20)
|
| HomeScreenKt$HomeScreen$7$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$HomeScreen$7$2 |
0%
(0/1)
|
|
| HomeScreenKt$HomeScreen$7$2$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$HomeScreen$7$3 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/44)
|
| HomeScreenKt$HomeScreen$8 |
|
| HomeScreenKt$HomeScreen$requestNearbyWifiPermissionLauncher$1 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/3)
|
0%
(0/10)
|
| HomeScreenKt$LongPressCopyableText$1 |
0%
(0/1)
|
0%
(0/2)
|
0%
(0/2)
|
0%
(0/44)
|
| HomeScreenKt$LongPressCopyableText$1$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/19)
|
| HomeScreenKt$LongPressCopyableText$2 |
|
| HomeScreenKt$NoConcurrencyWarningDialog$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$NoConcurrencyWarningDialog$2 |
0%
(0/1)
|
|
| HomeScreenKt$NoConcurrencyWarningDialog$2$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$NoConcurrencyWarningDialog$3 |
|
| HomeScreenKt$QRCodeView$2 |
|
| HomeScreenKt$StartHomeScreen$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/1)
|
| HomeScreenKt$StartHomeScreen$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/1)
|
| HomeScreenKt$StartHomeScreen$3 |
0%
(0/1)
|
|
0%
(0/1)
|
| HomeScreenKt$StartHomeScreen$4 |
0%
(0/1)
|
|
0%
(0/1)
|
| HomeScreenKt$StartHomeScreen$5 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/3)
|
| HomeScreenKt$StartHomeScreen$6 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/3)
|
| HomeScreenKt$StartHomeScreen$7 |
0%
(0/1)
|
|
0%
(0/7)
|
0%
(0/149)
|
| HomeScreenKt$StartHomeScreen$7$1 |
0%
(0/1)
|
|
0%
(0/12)
|
0%
(0/64)
|
| HomeScreenKt$StartHomeScreen$7$2 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$2$1$1$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| HomeScreenKt$StartHomeScreen$7$2$1$1$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/45)
|
| HomeScreenKt$StartHomeScreen$7$3 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$3$1$1$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| HomeScreenKt$StartHomeScreen$7$3$1$1$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/45)
|
| HomeScreenKt$StartHomeScreen$7$4 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$4$1$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/6)
|
| HomeScreenKt$StartHomeScreen$7$4$2$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/9)
|
| HomeScreenKt$StartHomeScreen$7$4$2$1$1 |
0%
(0/1)
|
0%
(0/2)
|
0%
(0/2)
|
0%
(0/8)
|
| HomeScreenKt$StartHomeScreen$7$5 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$5$1 |
0%
(0/1)
|
|
0%
(0/7)
|
0%
(0/35)
|
| HomeScreenKt$StartHomeScreen$7$6 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$6$1$1 |
0%
(0/1)
|
|
0%
(0/3)
|
0%
(0/12)
|
| HomeScreenKt$StartHomeScreen$7$6$2$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$StartHomeScreen$7$6$3 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/14)
|
| HomeScreenKt$StartHomeScreen$7$6$4 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/1)
|
0%
(0/52)
|
| HomeScreenKt$StartHomeScreen$7$6$5 |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/3)
|
0%
(0/62)
|
| HomeScreenKt$StartHomeScreen$7$6$6 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$6$7 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$7$6$7$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$StartHomeScreen$7$7 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$8 |
|
| HomeScreenKt$StartHomeScreen$connect$$inlined$instance$default$1 |
0%
(0/1)
|
|
| HomeScreenKt$StartHomeScreen$connectLauncher$1$1 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/4)
|
| HomeScreenKt$StartHomeScreen$qrScannerLauncher$1 |
0%
(0/1)
|
0%
(0/2)
|
0%
(0/5)
|
0%
(0/33)
|
| HomeScreenKt$StartHomeScreen$userEnteredConnectUri$2 |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/5)
|
| Total |
0%
(0/72)
|
0%
(0/30)
|
0%
(0/84)
|
0%
(0/776)
|
package com.greybox.projectmesh.views
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.wifi.WifiManager
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.zxing.BarcodeFormat
import com.greybox.projectmesh.extension.NEARBY_WIFI_PERMISSION_NAME
import com.greybox.projectmesh.R
import com.greybox.projectmesh.ViewModelFactory
import com.greybox.projectmesh.extension.hasNearbyWifiDevicesOrLocationPermission
import com.greybox.projectmesh.viewModel.HomeScreenViewModel
import com.journeyapps.barcodescanner.BarcodeEncoder
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.ustadmobile.meshrabiya.ext.addressToDotNotation
import com.ustadmobile.meshrabiya.vnet.AndroidVirtualNode
import com.ustadmobile.meshrabiya.vnet.MeshrabiyaConnectLink
import com.ustadmobile.meshrabiya.vnet.VirtualNode
import com.ustadmobile.meshrabiya.vnet.wifi.state.WifiStationState
import org.kodein.di.compose.localDI
import org.kodein.di.direct
import org.kodein.di.instance
import androidx.compose.runtime.State
import com.greybox.projectmesh.extension.hasStaApConcurrency
import com.greybox.projectmesh.ui.theme.TransparentButton
import com.greybox.projectmesh.viewModel.HomeScreenModel
import com.ustadmobile.meshrabiya.vnet.wifi.ConnectBand
import com.ustadmobile.meshrabiya.vnet.wifi.HotspotType
import com.greybox.projectmesh.components.ConnectWifiLauncherResult
import com.greybox.projectmesh.components.ConnectWifiLauncherStatus
import com.greybox.projectmesh.components.meshrabiyaConnectLauncher
import com.ustadmobile.meshrabiya.log.MNetLogger
@Composable
// We customize the viewModel since we need to inject dependencies
fun HomeScreen(
viewModel: HomeScreenViewModel = viewModel(
factory = ViewModelFactory(
di = localDI(),
owner = LocalSavedStateRegistryOwner.current,
vmFactory = { di, savedStateHandle -> HomeScreenViewModel(di, savedStateHandle) },
defaultArgs = null)),
deviceName: String?
)
{
val context = LocalContext.current
val di = localDI()
val uiState: HomeScreenModel by viewModel.uiState.collectAsState(initial = HomeScreenModel())
val node: VirtualNode by di.instance()
val logger: MNetLogger by di.instance()
val currConcurrencyKnown = viewModel.concurrencyKnown.collectAsState()
val currConcurrencySupported = viewModel.concurrencySupported.collectAsState()
// Request nearby wifi permission since it is necessary to start a hotspot
val requestNearbyWifiPermissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
){ granted -> if (granted){
if(context.hasNearbyWifiDevicesOrLocationPermission()){
viewModel.onSetIncomingConnectionsEnabled(true)
}
} }
// if not known and android version >= 11, then use official api to check concurrency
if(!currConcurrencyKnown.value && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
viewModel.saveConcurrencyKnown(true)
viewModel.saveConcurrencySupported(context.hasStaApConcurrency())
}
var errorMessage by remember { mutableStateOf<String?>(null) }
// Launch the home screen
StartHomeScreen(
uiState = uiState,
node = node as AndroidVirtualNode,
onSetIncomingConnectionsEnabled = { enabled ->
if(enabled) {
if (!context.hasNearbyWifiDevicesOrLocationPermission()) {
requestNearbyWifiPermissionLauncher.launch(NEARBY_WIFI_PERMISSION_NAME)
return@StartHomeScreen
}
if (uiState.hotspotTypeToCreate == HotspotType.WIFIDIRECT_GROUP) {
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
if (!wifiManager.isWifiEnabled) {
Toast.makeText(context, "Please Turn on Wifi", Toast.LENGTH_SHORT).show()
return@StartHomeScreen
}
}
}
viewModel.onSetIncomingConnectionsEnabled(enabled)
},
onClickDisconnectWifiStation = viewModel::onClickDisconnectStation,
deviceName = deviceName,
context = context,
currConcurrencyKnown = currConcurrencyKnown,
currConcurrencySupported = currConcurrencySupported,
onSetBand = viewModel::onConnectBandChanged,
onSetHotspotTypeToCreate = viewModel::onSetHotspotTypeToCreate,
onConnectWifiLauncherResult = { result ->
if(result.hotspotConfig != null) {
viewModel.onConnectWifi(result.hotspotConfig)
}else {
errorMessage = result.exception?.message
}
},
logger = logger
)
// Show an error dialog when needed
errorMessage?.let { message ->
AlertDialog(
onDismissRequest = { errorMessage = null },
title = { Text("Connection Error") },
text = { Text(message) },
confirmButton = {
TextButton(onClick = { errorMessage = null }) {
Text("OK")
}
}
)
}
}
// Display the home screen
@Composable
fun StartHomeScreen(
uiState: HomeScreenModel,
node: AndroidVirtualNode,
onSetIncomingConnectionsEnabled: (Boolean) -> Unit = { },
onClickDisconnectWifiStation: () -> Unit = { },
viewModel: HomeScreenViewModel = viewModel(),
deviceName: String?,
context: Context,
currConcurrencyKnown: State<Boolean>,
currConcurrencySupported: State<Boolean>,
onSetBand: (ConnectBand) -> Unit = { },
onSetHotspotTypeToCreate: (HotspotType) -> Unit = { },
onConnectWifiLauncherResult: (ConnectWifiLauncherResult) -> Unit,
logger: MNetLogger
){
val di = localDI()
val barcodeEncoder = remember { BarcodeEncoder() }
var connectLauncherState by remember {
mutableStateOf(ConnectWifiLauncherStatus.INACTIVE)
}
val connectLauncher = meshrabiyaConnectLauncher(
node = node,
logger = logger,
onStatusChange = {
connectLauncherState = it
},
onResult = onConnectWifiLauncherResult,
)
var userEnteredConnectUri by rememberSaveable { mutableStateOf("") }
val showNoConcurrencyWarning by viewModel.showNoConcurrencyWarning.collectAsState()
val showConcurrencyWarning by viewModel.showConcurrencyWarning.collectAsState()
// connect to other device via connect uri
fun connect(uri: String, logger: MNetLogger): Unit {
try {
logger(Log.INFO, "HomeScreen: Scanned link: $uri")
// Parse the link, get the wifi connect configuration.
val hotSpot = MeshrabiyaConnectLink.parseUri(
uri = uri,
json = di.direct.instance()
).hotspotConfig
// if the configuration is valid, connect to the device.
if (hotSpot != null) {
if(hotSpot.nodeVirtualAddr !in uiState.nodesOnMesh) {
// Connect device thru wifi connection
connectLauncher.launch(hotSpot)
}else{
Toast.makeText(context, "Already connected to this device", Toast.LENGTH_SHORT).show()
logger(Log.INFO,"Already connected to this device")
}
} else {
Toast.makeText(context, "Link doesn't have a connect config", Toast.LENGTH_SHORT).show()
logger(Log.WARN, "HomeScreen: Link doesn't have a connect config")
}
} catch (e: Exception) {
Toast.makeText(context, "Invalid Link", Toast.LENGTH_SHORT).show()
logger(Log.WARN, "HomeScreen: Invalid Link", e)
}
}
// initialize the QR code scanner
val qrScannerLauncher = rememberLauncherForActivityResult(contract = ScanContract()) { result ->
// Get the contents of the QR code
val link = result.contents
if (link != null) {
connect(link, logger)
}else{
Toast.makeText(context, "QR Code scan doesn't return a link", Toast.LENGTH_SHORT).show()
logger(Log.INFO,"QR Code scan doesn't return a link")
}
}
// Show warning popup when device does not support STA/AP concurrency
if (showNoConcurrencyWarning) {
NoConcurrencyWarningDialog(onDismiss = { viewModel.dismissNoConcurrencyWarning() })
}
// Show warning popup when device does not support STA/AP concurrency
if (showConcurrencyWarning) {
ConcurrencyWarningDialog(onDismiss = { viewModel.dismissConcurrencyWarning() })
}
// Function to check if Wi-Fi Direct is supported
fun isWifiDirectSupported(context: Context): Boolean {
return context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
item (key = "device_info") {
LongPressCopyableText(
context,
text = stringResource(id = R.string.device_name) + ": ",
textCopyable = deviceName.toString(),
textSize = 14,
padding = 6
)
LongPressCopyableText(
context,
text = stringResource(id = R.string.ip_address) + ": ",
textCopyable = uiState.localAddress.addressToDotNotation(),
textSize = 14,
padding = 6
)
}
item (key = "band_option"){
if (uiState.connectBandVisible) {
Text(
modifier = Modifier.padding(horizontal = 6.dp),
text = stringResource(id = R.string.band),
style = MaterialTheme.typography.bodySmall,
)
Row (modifier = Modifier.padding(horizontal = 6.dp)){
uiState.bandMenu.forEach { band ->
FilterChip(
selected = uiState.band == band,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp),
onClick = {
onSetBand(band)
},
label = {
Text(band.toString())
},
)
}
}
}
}
item (key = "hotspot_type_option") {
if(!uiState.wifiConnectionEnabled) {
val wifiDirectSupported = isWifiDirectSupported(context)
Text(
modifier = Modifier.padding(horizontal = 6.dp),
text = stringResource(id = R.string.hotspot_type),
style = MaterialTheme.typography.bodySmall,
)
Row(modifier = Modifier.padding(horizontal = 6.dp)){
uiState.hotspotTypeMenu.forEach { hotspotType ->
val isDisabled = (hotspotType == HotspotType.WIFIDIRECT_GROUP && !wifiDirectSupported)
FilterChip(
enabled = !isDisabled,
selected = hotspotType == uiState.hotspotTypeToCreate,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
onClick = { onSetHotspotTypeToCreate(hotspotType) },
label = { Text(hotspotType.toString()) }
)
}
}
}
}
item (key = "start_stop_hotspot_button"){
// Display the "Start Hotspot" button
val stationState = uiState.wifiState?.wifiStationState
if (!uiState.wifiConnectionEnabled) {
Row{
TransparentButton(
onClick = { onSetIncomingConnectionsEnabled(true) },
modifier = Modifier.padding(4.dp),
text = stringResource(id = R.string.start_hotspot),
// If not connected to a WiFi, enable the button
// Else, check if the device supports WiFi STA/AP Concurrency
// If it does, enable the button. Otherwise, disable it
enabled = if(stationState == null || stationState.status == WifiStationState.Status.INACTIVE)
true
else
currConcurrencySupported.value
)
}
}
// Display the "Stop Hotspot" button
else{
Row{
TransparentButton(
onClick = {
stopHotspotConfirmationDialog(context) { onConfirm ->
if (onConfirm) {
onSetIncomingConnectionsEnabled(false)
}
}
},
modifier = Modifier.padding(4.dp),
text = stringResource(id = R.string.stop_hotspot),
enabled = true
)
}
}
}
item(key = "qr_code_view"){
// Generating QR CODE
val connectUri = uiState.connectUri
if (connectUri != null && uiState.wifiConnectionEnabled) {
QRCodeView(
connectUri,
barcodeEncoder,
uiState.wifiState?.connectConfig?.ssid,
uiState.wifiState?.connectConfig?.passphrase,
uiState.wifiState?.connectConfig?.bssid,
uiState.wifiState?.connectConfig?.port.toString()
)
// Display connectUri
Spacer(modifier = Modifier.height(16.dp))
Text(text = stringResource(id = R.string.instruction_start_hotspot))
Button(onClick = {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, connectUri)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
context.startActivity(shareIntent)
}, modifier = Modifier.padding(4.dp), enabled = true) {
Text(stringResource(id = R.string.share_connect_uri))
}
}
}
item(key = "connect_via_qr_code") {
val stationState = uiState.wifiState?.wifiStationState
// Scan the QR CODE
// If the stationState is not null and its status is INACTIVE,
// It will display the option to connect via a QR code scan.
if (stationState != null){
if (stationState.status == WifiStationState.Status.INACTIVE){
Text(modifier = Modifier.padding(6.dp), text = stringResource(id = R.string.wifi_station_connection), style = TextStyle(fontSize = 16.sp))
Spacer(modifier = Modifier.height(12.dp))
Row{
TransparentButton(onClick = {
qrScannerLauncher.launch(ScanOptions().setOrientationLocked(false)
.setPrompt("Scan another device to join the Mesh")
.setBeepEnabled(true)
)},
modifier = Modifier.padding(4.dp),
text = stringResource(id = R.string.connect_via_qr_code_scan),
// If the hotspot isn't started, enable the button
// Else, check if the device supports WiFi STA/AP Concurrency
// If it does, enable the button. Otherwise, disable it
enabled = if(!uiState.hotspotStatus)
true
else
currConcurrencySupported.value
)
}
Text(modifier = Modifier.padding(6.dp), text = stringResource(id = R.string.instruction))
TextField(
value = userEnteredConnectUri,
onValueChange = {
userEnteredConnectUri = it
},
label = { Text(stringResource(id = R.string.prompt_enter_uri)) }
)
TransparentButton(
onClick = {
connect(userEnteredConnectUri, logger)
},
modifier = Modifier.padding(4.dp),
text = stringResource(id = R.string.connect_via_entering_connect_uri),
// If the hotspot isn't started, enable the button
// Else, check if the device supports WiFi STA/AP Concurrency
// If it does, enable the button. Otherwise, disable it
enabled = if (!uiState.hotspotStatus)
true
else
currConcurrencySupported.value
)
}
// If the stationState is not INACTIVE, it displays a ListItem that represents
// the current connection status.
else{
ListItem(
headlineContent = {
Text(stationState.config?.ssid ?: "(Unknown SSID)")
},
supportingContent = {
Text(
(stationState.config?.nodeVirtualAddr?.addressToDotNotation() ?: "") +
" - ${stationState.status}"
)
},
leadingContent = {
if(stationState.status == WifiStationState.Status.CONNECTING) {
CircularProgressIndicator(
modifier = Modifier.padding(8.dp)
)
}else {
Icon(
imageVector = Icons.Default.Wifi,
contentDescription = "",
)
}
},
trailingContent = {
IconButton(
onClick = {
onClickDisconnectWifiStation()
}
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Disconnect",
)
}
}
)
}
}
}
// add a Hotspot status indicator
item(key = "hotspot_status_indicator"){
Row(verticalAlignment = Alignment.CenterVertically) {
Text(modifier = Modifier.padding(6.dp),
text = stringResource(id = R.string.hotspot_status) + ": " +
if (uiState.hotspotStatus) stringResource(
id = R.string.hotspot_status_online)
else stringResource(id = R.string.hotspot_status_offline))
Box(
modifier = Modifier.size(8.dp).background(
color = if (uiState.hotspotStatus) Color.Green else Color.Red,
shape = CircleShape
)
)
}
}
}
}
fun stopHotspotConfirmationDialog(context: Context, onConfirm: (Boolean) -> Unit){
AlertDialog.Builder(context)
.setTitle("Do you want to turn off the hotspot?")
.setPositiveButton("Yes"){ _, _ ->
onConfirm(true)
}
.setNegativeButton("No"){ _, _ ->
onConfirm(false)
}
.show()
}
// Enable users to copy text by holding down the text for a long press
@Composable
fun LongPressCopyableText(context: Context,
text: String,
textCopyable: String,
textSize: Int,
padding: Int = 0){
val clipboardManager = LocalClipboardManager.current
BasicText(
text = text + textCopyable,
style = TextStyle(
fontSize = textSize.sp,
color = MaterialTheme.colorScheme.onBackground),
modifier = Modifier
.pointerInput(textCopyable) {
detectTapGestures(
onLongPress = {
clipboardManager.setText(AnnotatedString(textCopyable))
Toast
.makeText(context, "Text copied to clipboard!", Toast.LENGTH_SHORT)
.show()
})
}
.padding(padding.dp)
)
}
// display the QR code
@Composable
fun QRCodeView(qrcodeUri: String, barcodeEncoder: BarcodeEncoder, ssid: String?, password: String?,
mac: String?, port: String?) {
val configuration = LocalConfiguration.current
val screenWidthDp = configuration.screenWidthDp.dp
val density = LocalDensity.current
// Convert dp to int once and remember the value
val qrCodeSize = remember(density, screenWidthDp) {
with(density) { screenWidthDp.times(0.35f).roundToPx() } // Converts to Int
}
val qrCodeBitMap = remember(qrcodeUri) {
barcodeEncoder.encodeBitmap(
qrcodeUri, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize
).asImageBitmap()
}
Row (modifier = Modifier.fillMaxWidth()) {
// QR Code left side, Device info on the right side
Image(
bitmap = qrCodeBitMap,
contentDescription = "QR Code"
)
Spacer(modifier = Modifier.width(8.dp))
Column(
modifier = Modifier.weight(1f)
) {
Spacer(modifier = Modifier.height(10.dp))
Text(text = "SSID: $ssid")
Spacer(modifier = Modifier.height(10.dp))
Text(text = "Password: $password")
Spacer(modifier = Modifier.height(10.dp))
Text(text = "MAC: $mac")
Spacer(modifier = Modifier.height(10.dp))
Text(text = "Port: $port")
}
}
}
@Composable
fun NoConcurrencyWarningDialog(onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = { onDismiss() },
title = { Text("STA/AP Concurrency Not Supported") },
text = {
Text(
"Based on our test, we detected that your device does not support simultaneous Wi-Fi and hotspot usage (STA/AP concurrency)."
)
},
confirmButton = {
TextButton(onClick = { onDismiss() }) {
Text("OK")
}
}
)
}
@Composable
fun ConcurrencyWarningDialog(onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = { onDismiss() },
title = { Text("STA/AP Concurrency Supported") },
text = {
Text(
"Based on our test, we detected that your device support simultaneous Wi-Fi and hotspot usage (STA/AP concurrency)."
)
},
confirmButton = {
TextButton(onClick = { onDismiss() }) {
Text("OK")
}
}
)
}