Coverage Summary for Class: SendScreenKt (release.com.greybox.projectmesh.views)

Class Method, % Branch, % Line, % Instruction, %
SendScreenKt$DisplayAllPendingTransfers$1 0% (0/3)
SendScreenKt$DisplayAllPendingTransfers$1$1 0% (0/1) 0% (0/1) 0% (0/3)
SendScreenKt$DisplayAllPendingTransfers$1$2$1 0% (0/1) 0% (0/6) 0% (0/35)
SendScreenKt$DisplayAllPendingTransfers$1$2$1$1 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$1$2$1$1$1 0% (0/1) 0% (0/1) 0% (0/45)
SendScreenKt$DisplayAllPendingTransfers$1$2$1$1$2 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$1$2$1$1$2$1$deviceName$1$1$1 0% (0/1) 0% (0/4) 0% (0/2) 0% (0/35)
SendScreenKt$DisplayAllPendingTransfers$1$2$swipeState$1 0% (0/1) 0% (0/2) 0% (0/4) 0% (0/26)
SendScreenKt$DisplayAllPendingTransfers$1$2$swipeState$1$1 0% (0/1) 0% (0/2) 0% (0/4) 0% (0/35)
SendScreenKt$DisplayAllPendingTransfers$1$invoke$$inlined$items$default$1 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$1$invoke$$inlined$items$default$2 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$1$invoke$$inlined$items$default$3 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$1$invoke$$inlined$items$default$4 0% (0/1)
SendScreenKt$DisplayAllPendingTransfers$2
SendScreenKt$SendScreen$1$1 0% (0/1) 0% (0/1) 0% (0/7)
SendScreenKt$SendScreen$2$2 0% (0/1) 0% (0/1) 0% (0/11)
SendScreenKt$SendScreen$3
SendScreenKt$SendScreen$openDocumentLauncher$1 0% (0/1) 0% (0/2) 0% (0/2) 0% (0/9)
Total 0% (0/23) 0% (0/10) 0% (0/22) 0% (0/206)


 package com.greybox.projectmesh.views
 
 import android.net.Uri
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.LinearOutSlowInEasing
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 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.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material3.DismissValue
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.Icon
 import androidx.compose.material3.ListItem
 import androidx.compose.material3.SwipeToDismiss
 import androidx.compose.material3.SwipeToDismissBox
 import androidx.compose.material3.SwipeToDismissBoxValue
 import androidx.compose.material3.Text
 import androidx.compose.material3.rememberSwipeToDismissBoxState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 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.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.greybox.projectmesh.GlobalApp
 import com.greybox.projectmesh.R
 import com.greybox.projectmesh.ViewModelFactory
 import com.greybox.projectmesh.ui.theme.TransparentButton
 import com.greybox.projectmesh.viewModel.SendScreenModel
 import com.greybox.projectmesh.viewModel.SendScreenViewModel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.kodein.di.compose.localDI
 
 @Composable
 fun SendScreen(
     onSwitchToSelectDestNode: (List<Uri>) -> Unit,
     viewModel: SendScreenViewModel = viewModel(
         factory = ViewModelFactory(
             di = localDI(),
             owner = LocalSavedStateRegistryOwner.current,
             vmFactory = { di, savedStateHandle -> SendScreenViewModel(di, savedStateHandle, onSwitchToSelectDestNode)},
             defaultArgs = null,
         )
     ),
 ) {
     // declare the UI state
     val uiState: SendScreenModel by viewModel.uiState.collectAsState(SendScreenModel())
     // File picker launcher
     val openDocumentLauncher = rememberLauncherForActivityResult(
         ActivityResultContracts.OpenMultipleDocuments()
     ){ uris ->
         if (uris.isNotEmpty()){
             viewModel.onFileChosen(uris)
         }
     }
     Box(modifier = Modifier.fillMaxSize()){
         Column(modifier = Modifier
             .fillMaxSize()
             .padding(bottom = 72.dp)) {
             DisplayAllPendingTransfers(viewModel, uiState)
         }
         TransparentButton(onClick = { openDocumentLauncher.launch(arrayOf("*/*")) },
             modifier = Modifier
                 .align(Alignment.BottomCenter)
                 .padding(16.dp),
             text = stringResource(id = R.string.send_file),
             enabled = true
         )
     }
 }
 
 @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
 @Composable
 // Display all the pending transfers
 fun DisplayAllPendingTransfers(
     viewModel: SendScreenViewModel,
     uiState: SendScreenModel,
 ){
     val coroutineScope = rememberCoroutineScope()
     LazyColumn {
         items(
             items = uiState.outgoingTransfers,
             key = { it.id }
         ) {transfer ->
             var isVisible by remember { mutableStateOf(true) }
             val swipeState = rememberSwipeToDismissBoxState(
                 confirmValueChange = { dismissValue ->
                     if (dismissValue == SwipeToDismissBoxValue.EndToStart) {
                         coroutineScope.launch {
                             isVisible = false // Start fade-out animation
                             delay(300)
                             viewModel.onDelete(transfer) // Remove item on swipe
                         }
                         true
                     } else {
                         false
                     }
                 }
             )
             AnimatedVisibility(
                 visible = isVisible,
                 exit = fadeOut(animationSpec = tween(300)),
                 modifier = Modifier.animateItemPlacement()
             ){
                 SwipeToDismissBox(
                     state = swipeState,
                     enableDismissFromStartToEnd = false, // Allow swipe only from right to left
                     enableDismissFromEndToStart = true,
                     backgroundContent = {
                         Box(
                             modifier = Modifier
                                 .fillMaxSize()
                                 .height(64.dp) // Controls the red background size
                                 .padding(vertical = 8.dp) // Prevents red from touching top & bottom
                                 .background(Color.Red, shape = RoundedCornerShape(12.dp))
                                 .border(width = 0.dp, color = Color.Transparent, shape = RoundedCornerShape(12.dp)),
                             contentAlignment = Alignment.CenterEnd
                         ) {
                             Icon(
                                 imageVector = Icons.Default.Delete,
                                 contentDescription = "Delete",
                                 tint = Color.White,
                                 modifier = Modifier.size(32.dp).padding(end = 6.dp)
                             )
                         }
                     },
                     content = {
                         ListItem(
                             headlineContent = { Text(transfer.name) },
                             supportingContent = {
                                 Column {
                                     val byteTransferred: Int = transfer.transferred
                                     val byteSize: Int = transfer.size
                                     val toHostAddress = transfer.toHost.hostAddress
                                     val deviceName = toHostAddress?.let { ipStr ->
                                         runBlocking {
                                             GlobalApp.GlobalUserRepo.userRepository.getUserByIp(ipStr)?.name
                                         }
                                     }
 //                                    val deviceName = toHostAddress?.let {
 //                                        GlobalApp.DeviceInfoManager.getDeviceName(it)
 //                                    }
                                     if (deviceName != null) {
                                         Text("To: ${deviceName} (${toHostAddress})")
                                     } else {
                                         Text("To: Loading... (${toHostAddress})")
                                     }
                                     Text(stringResource(id = R.string.status) + ": ${transfer.status}")
                                     Text(stringResource(id = R.string.send) + ": ${autoConvertByte(byteTransferred)} / ${autoConvertByte(byteSize)}")
                                 }
                             },
                             modifier = Modifier.padding(vertical = 4.dp)
                         )
                     },
                 )
                 HorizontalDivider()
             }
         }
     }
 }
 
 fun autoConvertByte(byteSize: Int): String{
     val kb = Math.round(byteSize / 1024.0 * 100) / 100.0
     val mb = Math.round((byteSize / (1024.0 * 1024.0) * 100) / 100.0)
     if (byteSize == 0){
         return "0B"
     }
     else if (mb < 1){
         return "${kb}KB"
     }
     return "${mb}MB"
 }
 
 fun autoConvertMS(ms: Int): String {
     val second = Math.round(ms / 1000.0 * 100) / 100.0
     val minute = Math.round((second / 60.0) * 100) / 100.0
     return if (second >= 1 && minute < 1) {
         "${second}s"
     } else if (minute >= 1) {
         "${minute}m"
     } else {
         "${ms}ms"
     }
 }