Coverage Summary for Class: SettingsScreenKt (debug.com.greybox.projectmesh.views)

Class Method, % Branch, % Line, % Instruction, %
SettingsScreenKt$ChangeDeviceNameDialog$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$ChangeDeviceNameDialog$2 0% (0/1) 0% (0/3) 0% (0/53)
SettingsScreenKt$ChangeDeviceNameDialog$2$1 0% (0/3)
SettingsScreenKt$ChangeDeviceNameDialog$2$1$1$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$ChangeDeviceNameDialog$2$1$1$2 0% (0/1) 0% (0/1) 0% (0/49)
SettingsScreenKt$ChangeDeviceNameDialog$2$1$1$3$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$ChangeDeviceNameDialog$2$1$1$3$2$1 0% (0/1) 0% (0/2) 0% (0/2) 0% (0/14)
SettingsScreenKt$ChangeDeviceNameDialog$3
SettingsScreenKt$LanguageSetting$1$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$LanguageSetting$1$2$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$LanguageSetting$1$3 0% (0/1)
SettingsScreenKt$LanguageSetting$1$3$1$1 0% (0/1) 0% (0/1) 0% (0/46)
SettingsScreenKt$LanguageSetting$1$3$1$2$1 0% (0/1) 0% (0/2) 0% (0/11)
SettingsScreenKt$LanguageSetting$2
SettingsScreenKt$SectionHeader$1
SettingsScreenKt$SettingItem$2
SettingsScreenKt$SettingsScreen$$inlined$instance$1 0% (0/1)
SettingsScreenKt$SettingsScreen$1 0% (0/1) 0% (0/1) 0% (0/5)
SettingsScreenKt$SettingsScreen$2$1$1 0% (0/1) 0% (0/2) 0% (0/34)
SettingsScreenKt$SettingsScreen$2$1$1$1 0% (0/1) 0% (0/2) 0% (0/9)
SettingsScreenKt$SettingsScreen$2$1$2 0% (0/1) 0% (0/2) 0% (0/34)
SettingsScreenKt$SettingsScreen$2$1$2$1 0% (0/1) 0% (0/2) 0% (0/9)
SettingsScreenKt$SettingsScreen$2$1$3 0% (0/1) 0% (0/3) 0% (0/38)
SettingsScreenKt$SettingsScreen$2$1$4 0% (0/1)
SettingsScreenKt$SettingsScreen$2$1$4$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$SettingsScreen$2$1$5$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$SettingsScreen$2$1$6 0% (0/1) 0% (0/3) 0% (0/13)
SettingsScreenKt$SettingsScreen$2$1$7 0% (0/1)
SettingsScreenKt$SettingsScreen$2$1$7$1$1 0% (0/1) 0% (0/2) 0% (0/10)
SettingsScreenKt$SettingsScreen$2$1$8 0% (0/1) 0% (0/1) 0% (0/35)
SettingsScreenKt$SettingsScreen$2$1$8$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$SettingsScreen$2$1$9 0% (0/1) 0% (0/2) 0% (0/38)
SettingsScreenKt$SettingsScreen$2$1$9$1 0% (0/1) 0% (0/6) 0% (0/12)
SettingsScreenKt$SettingsScreen$2$1$directoryLauncher$1 0% (0/1) 0% (0/2) 0% (0/8) 0% (0/47)
SettingsScreenKt$SettingsScreen$3
SettingsScreenKt$ThemeSetting$1$1$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$ThemeSetting$1$2$1 0% (0/1) 0% (0/1) 0% (0/4)
SettingsScreenKt$ThemeSetting$1$3 0% (0/1)
SettingsScreenKt$ThemeSetting$1$3$1$1 0% (0/1) 0% (0/1) 0% (0/49)
SettingsScreenKt$ThemeSetting$1$3$1$2$1 0% (0/1) 0% (0/2) 0% (0/10)
SettingsScreenKt$ThemeSetting$2
Total 0% (0/49) 0% (0/4) 0% (0/56) 0% (0/556)


 package com.greybox.projectmesh.views
 
 import android.content.Intent
 import android.content.SharedPreferences
 import android.graphics.Paint.Align
 import android.net.Uri
 import android.os.Build
 import android.widget.Toast
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 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.width
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.HorizontalDivider
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Switch
 import androidx.compose.material3.SwitchDefaults
 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.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalSavedStateRegistryOwner
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.window.Dialog
 import androidx.compose.ui.window.PopupProperties
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.greybox.projectmesh.R
 import com.greybox.projectmesh.ViewModelFactory
 import com.greybox.projectmesh.ui.theme.AppTheme
 import com.greybox.projectmesh.ui.theme.GradientButton
 import com.greybox.projectmesh.ui.theme.GradientLongButton
 import com.greybox.projectmesh.viewModel.HomeScreenViewModel
 import com.greybox.projectmesh.viewModel.SendScreenViewModel
 import com.greybox.projectmesh.viewModel.SettingsScreenViewModel
 import org.kodein.di.compose.localDI
 import org.kodein.di.instance
 
 
 @Composable
 fun SettingsScreen(
     viewModel: SettingsScreenViewModel = viewModel(
         factory = ViewModelFactory(
             di = localDI(),
             owner = LocalSavedStateRegistryOwner.current,
             vmFactory = { di, savedStateHandle -> SettingsScreenViewModel(di, savedStateHandle)},
             defaultArgs = null,
         )),
     onThemeChange: (AppTheme) -> Unit,
     onLanguageChange: (String) -> Unit,
     onRestartServer: () -> Unit,
     onDeviceNameChange: (String) -> Unit,
     onAutoFinishChange: (Boolean) -> Unit,
     onSaveToFolderChange: (String) -> Unit
 ) {
     val di = localDI()
     val context = LocalContext.current
     val currTheme = viewModel.theme.collectAsState()
     val currLang = viewModel.lang.collectAsState()
     val currDeviceName = viewModel.deviceName.collectAsState()
     val currAutoFinish = viewModel.autoFinish.collectAsState()
     val currSaveToFolder = viewModel.saveToFolder.collectAsState()
 
     var showDialog by remember { mutableStateOf(false) }
 
     val settingPref: SharedPreferences by di.instance(tag = "settings")
 
     Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()))
     {
         Spacer(modifier = Modifier.height(36.dp))
         // Title "Settings"
         Text(
             text = stringResource(id = R.string.settings),
             style = TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold),
             modifier = Modifier.align(Alignment.CenterHorizontally)
         )
         Column(modifier = Modifier.padding(36.dp)) {
             // General Setting Section (Language, Theme)
             SectionHeader(title = R.string.general)
             SettingItem(
                 label = stringResource(id = R.string.language),
                 trailingContent = {
                     LanguageSetting(
                         currentLanguage = currLang.value,
                         onLanguageSelected = { selectedLanguageCode ->
                             viewModel.saveLang(selectedLanguageCode)
                             onLanguageChange(selectedLanguageCode)
                         }
                     )
                 }
             )
             SettingItem(
                 label = stringResource(id = R.string.theme),
                 trailingContent = {
                     ThemeSetting(
                         currentTheme = currTheme.value,
                         onThemeSelected = { selectedTheme ->
                             viewModel.saveTheme(selectedTheme)
                             onThemeChange(selectedTheme)
                         }
                     )
                 }
             )
             // Network Setting Section (Server Restart, Device Name Change)
             SectionHeader(title = R.string.network)
             SettingItem(
                 label = stringResource(id = R.string.server),
                 trailingContent = {
                     GradientButton(
                         text = stringResource(id = R.string.restart),
                         onClick = onRestartServer
                     )
                 }
             )
             SettingItem(
                 label = stringResource(id = R.string.device_name),
                 trailingContent = {
                     GradientButton(text = currDeviceName.value, onClick = { showDialog = true })
                 }
             )
             if (showDialog) {
                 ChangeDeviceNameDialog(
                     onDismiss = { showDialog = false },
                     onConfirm = { newDeviceName ->
                         viewModel.saveDeviceName(newDeviceName)
                         onDeviceNameChange(newDeviceName)
                         showDialog = false
                     },
                     deviceName = currDeviceName.value
                 )
             }
             // Receive Setting Section (Auto Finish, Save to folder)
             SectionHeader(title = R.string.receive)
             SettingItem(label = stringResource(id = R.string.auto_finish),
                 trailingContent = {
                     Box(modifier = Modifier.width(130.dp).height(70.dp))
                     {
                         Switch(
                             checked = currAutoFinish.value,
                             onCheckedChange = { isChecked ->
                                 viewModel.saveAutoFinish(isChecked)
                                 onAutoFinishChange(isChecked)
                             },
                             colors = SwitchDefaults.colors(
                                 checkedThumbColor = Color.White,
                                 uncheckedThumbColor = Color.White,
                                 checkedTrackColor = Color(0xFF4CAF50),
                                 uncheckedTrackColor = Color.LightGray,
                             ),
                             modifier = Modifier.scale(1.3f).align(Alignment.Center)
                         )
                     }
                 }
             )
             val directoryLauncher = rememberLauncherForActivityResult(
                 contract = ActivityResultContracts.OpenDocumentTree()
             ) { uri: Uri? ->
                 if (uri != null) {
                     // Persist the directory permission
                     context.contentResolver.takePersistableUriPermission(
                         uri,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     )
                     Toast.makeText(context, "Directory selected: $uri", Toast.LENGTH_LONG).show()
                     viewModel.saveSaveToFolder(uri.toString())
                     onSaveToFolderChange(uri.toString())
                 } else {
                     Toast.makeText(context, "No directory selected", Toast.LENGTH_LONG).show()
                 }
             }
             val folderNameToShow = if (currSaveToFolder.value.startsWith("content://")) {
                 // Decode Uri and extract the folder name for content URIs
                 Uri.decode(currSaveToFolder.value).split(":").lastOrNull() ?: "Unknown"
             } else {
                 // Extract the last directory from the file path for plain file paths
                 currSaveToFolder.value.split("/").lastOrNull() ?: "Unknown"
             }
             SettingItem(label = stringResource(id = R.string.save_to_folder),
                 trailingContent = {
                     GradientButton(text = folderNameToShow,
                         onClick = { directoryLauncher.launch(null) }
                     )
                 }
             )
             // STA/AP Concurrency Setting Section (Only for Android 10 and below)
             if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                 SectionHeader(title = R.string.concurrency)
                 SettingItem(label = "",
                     trailingContent = {
                         GradientLongButton(
                             text = stringResource(id = R.string.reset),
                             onClick = {
                                 viewModel.updateConcurrencySettings(false, true)
                                 Toast.makeText(
                                     context,
                                     "Reset STA/AP Concurrency Status -> Unknown",
                                     Toast.LENGTH_SHORT
                                 ).show()
                             }
                         )
                     }
                 )
             }
         }
     }
 }
 @Composable
 fun SectionHeader(title: Int) {
     Spacer(modifier = Modifier.height(20.dp))
     Text(
         text = stringResource(id = title),
         style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold)
     )
     HorizontalDivider(
         modifier = Modifier
             .fillMaxWidth()
             .padding(0.dp, 10.dp),
         thickness = 2.dp,
         color = Color.Red
     )
 }
 
 @Composable
 fun SettingItem(label: String, trailingContent: @Composable () -> Unit) {
     Row(
         modifier = Modifier
             .fillMaxWidth()
             .padding(vertical = 16.dp),
         verticalAlignment = Alignment.CenterVertically
     ) {
         Text(text = label, style = TextStyle(fontSize = 18.sp))
         Spacer(modifier = Modifier.weight(1f))
         trailingContent()
     }
 }
 
 @Composable
 fun LanguageSetting(
     currentLanguage: String,
     onLanguageSelected: (String) -> Unit)
 {
     // for Language setting
     var langExpanded by remember { mutableStateOf(false) } // Track menu visibility
     val langMenuItems = listOf("en" to "English", "es" to "Español", "cn" to "简体中文", "fr" to
             "Français") //
     // Menu
     // items
     val langSelectedOption = langMenuItems.firstOrNull {it.first == currentLanguage}?.second?:"English"
     Box()
     {
         GradientButton(text = langSelectedOption,
             onClick = { langExpanded = true })
         DropdownMenu(expanded = langExpanded,
             onDismissRequest = { langExpanded = false },
             properties = PopupProperties(true))
         {
             langMenuItems.forEach{ item ->
                 DropdownMenuItem(
                     text = { Text(item.second) },
                     onClick = {
                         onLanguageSelected(item.first)
                         langExpanded = false
                     }
                 )
             }
         }
     }
 }
 
 @Composable
 fun ThemeSetting(
     currentTheme: AppTheme,
     onThemeSelected: (AppTheme) -> Unit)
 {
     // for Theme setting
     var expanded by remember { mutableStateOf(false) } // Track menu visibility
     val themes = listOf("System", "Light", "Dark") // Menu items
     Box()
     {
         GradientButton(text = themes[currentTheme.ordinal],
             onClick = { expanded = true })
         DropdownMenu(expanded = expanded,
             onDismissRequest = { expanded = false },
             properties = PopupProperties(true))
         {
             AppTheme.entries.forEach{ theme ->
                 DropdownMenuItem(
                     text = { Text(themes[theme.ordinal]) },
                     onClick = {
                         onThemeSelected(theme)
                         expanded = false
                     }
                 )
             }
         }
     }
 }
 
 @Composable
 fun ChangeDeviceNameDialog(
     onDismiss: () -> Unit,
     onConfirm: (String) -> Unit,
     deviceName: String,
 ){
     Dialog(onDismissRequest = { onDismiss() }) {
         Surface(
             shape = MaterialTheme.shapes.large,
         ) {
             Column(
                 modifier = Modifier
                     .padding(16.dp)
                     .fillMaxWidth(),
                 verticalArrangement = Arrangement.spacedBy(16.dp)
             ) {
                 Text("Enter New Device Name", style = MaterialTheme.typography.titleMedium)
                 var inputText by remember { mutableStateOf("") }
                 TextField(
                     value = inputText,
                     onValueChange = { inputText = it },
                     label = { Text(deviceName) },
                     modifier = Modifier.fillMaxWidth()
                 )
 
                 Row(
                     modifier = Modifier.fillMaxWidth(),
                     horizontalArrangement = Arrangement.End
                 ) {
                     TextButton(onClick = { onDismiss() }) {
                         Text("Cancel")
                     }
                     TextButton(onClick = {
                         if (inputText.isNotBlank()) {
                             onConfirm(inputText)
                         }
                     }) {
                         Text("Submit")
                     }
                 }
             }
         }
     }
 }