kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
rodzic
17dc4da191
commit
4bcd408dce
|
@ -49,7 +49,7 @@ interface IMeshService {
|
|||
|
||||
If myId is null, then the existing unique node ID is preserved, only the human visible longName/shortName is changed
|
||||
*/
|
||||
void setOwner(String myId, String longName, String shortName);
|
||||
void setOwner(String myId, String longName, String shortName, boolean isLicensed);
|
||||
|
||||
/// Return my unique user ID string
|
||||
String getMyId();
|
||||
|
|
|
@ -18,11 +18,12 @@ data class MeshUser(
|
|||
val id: String,
|
||||
val longName: String,
|
||||
val shortName: String,
|
||||
val hwModel: MeshProtos.HardwareModel
|
||||
val hwModel: MeshProtos.HardwareModel,
|
||||
val isLicensed: Boolean,
|
||||
) : Parcelable {
|
||||
|
||||
override fun toString(): String {
|
||||
return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize}, hwModel=${hwModelString})"
|
||||
return "MeshUser(id=${id.anonymize}, longName=${longName.anonymize}, shortName=${shortName.anonymize}, hwModel=${hwModelString}, isLicensed=${isLicensed})"
|
||||
}
|
||||
|
||||
/** a string version of the hardware model, converted into pretty lowercase and changing _ to -, and p to dot
|
||||
|
|
|
@ -6,25 +6,21 @@ import com.geeksville.mesh.R
|
|||
enum class ChannelOption(
|
||||
val modemPreset: ModemPreset,
|
||||
val configRes: Int,
|
||||
val minBroadcastPeriodSecs: Int
|
||||
) {
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, R.string.modem_config_short, 30),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, R.string.modem_config_slow_short, 30),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, R.string.modem_config_medium, 60),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, R.string.modem_config_slow_medium, 60),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, R.string.modem_config_long, 60),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.modem_config_slow_long, 240),
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.modem_config_very_long, 375);
|
||||
SHORT_FAST(ModemPreset.SHORT_FAST, R.string.modem_config_short),
|
||||
SHORT_SLOW(ModemPreset.SHORT_SLOW, R.string.modem_config_slow_short),
|
||||
MEDIUM_FAST(ModemPreset.MEDIUM_FAST, R.string.modem_config_medium),
|
||||
MEDIUM_SLOW(ModemPreset.MEDIUM_SLOW, R.string.modem_config_slow_medium),
|
||||
LONG_FAST(ModemPreset.LONG_FAST, R.string.modem_config_long),
|
||||
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.modem_config_slow_long),
|
||||
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.modem_config_very_long);
|
||||
|
||||
companion object {
|
||||
fun fromConfig(modemPreset: ModemPreset?): ChannelOption? {
|
||||
for (option in values()) {
|
||||
if (option.modemPreset == modemPreset)
|
||||
return option
|
||||
if (option.modemPreset == modemPreset) return option
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
val defaultMinBroadcastPeriod = VERY_LONG_SLOW.minBroadcastPeriodSecs
|
||||
}
|
||||
}
|
|
@ -22,7 +22,8 @@ class NodeDB(private val ui: UIViewModel) {
|
|||
"+16508765308".format(8),
|
||||
"Kevin MesterNoLoc",
|
||||
"KLO",
|
||||
MeshProtos.HardwareModel.ANDROID_SIM
|
||||
MeshProtos.HardwareModel.ANDROID_SIM,
|
||||
false
|
||||
),
|
||||
null
|
||||
)
|
||||
|
@ -34,7 +35,8 @@ class NodeDB(private val ui: UIViewModel) {
|
|||
"+165087653%02d".format(9 + index),
|
||||
"Kevin Mester$index",
|
||||
"KM$index",
|
||||
MeshProtos.HardwareModel.ANDROID_SIM
|
||||
MeshProtos.HardwareModel.ANDROID_SIM,
|
||||
false
|
||||
),
|
||||
it
|
||||
)
|
||||
|
|
|
@ -34,7 +34,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.osmdroid.bonuspack.kml.KmlDocument
|
||||
|
@ -110,21 +112,17 @@ class UIViewModel @Inject constructor(
|
|||
_packets.value = packets
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
localConfigRepository.localConfigFlow.collect { config ->
|
||||
_localConfig.value = config
|
||||
}
|
||||
}
|
||||
localConfigRepository.localConfigFlow.onEach { config ->
|
||||
_localConfig.value = config
|
||||
}.launchIn(viewModelScope)
|
||||
viewModelScope.launch {
|
||||
quickChatActionRepository.getAllActions().collect { actions ->
|
||||
_quickChatActions.value = actions
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
channelSetRepository.channelSetFlow.collect { channelSet ->
|
||||
_channels.value = ChannelSet(channelSet)
|
||||
}
|
||||
}
|
||||
channelSetRepository.channelSetFlow.onEach { channelSet ->
|
||||
_channels.value = ChannelSet(channelSet)
|
||||
}.launchIn(viewModelScope)
|
||||
debug("ViewModel created")
|
||||
}
|
||||
|
||||
|
@ -240,7 +238,7 @@ class UIViewModel @Inject constructor(
|
|||
val isRouter: Boolean = config.device.role == Config.DeviceConfig.Role.ROUTER
|
||||
|
||||
// We consider hasWifi = ESP32
|
||||
fun isESP32() = myNodeInfo.value?.hasWifi == true
|
||||
fun hasWifi() = myNodeInfo.value?.hasWifi == true
|
||||
|
||||
/// hardware info about our local device (can be null)
|
||||
private val _myNodeInfo = MutableLiveData<MyNodeInfo?>()
|
||||
|
@ -364,14 +362,14 @@ class UIViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
// clean up all this nasty owner state management FIXME
|
||||
fun setOwner(s: String? = null) {
|
||||
fun setOwner(longName: String? = null, shortName: String? = null, isLicensed: Boolean? = null) {
|
||||
|
||||
if (s != null) {
|
||||
_ownerName.value = s
|
||||
if (longName != null) {
|
||||
_ownerName.value = longName
|
||||
|
||||
// note: we allow an empty userstring to be written to prefs
|
||||
preferences.edit {
|
||||
putString("owner", s)
|
||||
putString("owner", longName)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -381,7 +379,8 @@ class UIViewModel @Inject constructor(
|
|||
meshService?.setOwner(
|
||||
null,
|
||||
_ownerName.value,
|
||||
getInitials(_ownerName.value!!)
|
||||
shortName ?: getInitials(_ownerName.value!!),
|
||||
isLicensed ?: false
|
||||
) // Note: we use ?. here because we might be running in the emulator
|
||||
} catch (ex: RemoteException) {
|
||||
errormsg("Can't set username on device, is device offline? ${ex.message}")
|
||||
|
|
|
@ -732,7 +732,8 @@ class MeshService : Service(), Logging {
|
|||
p.id.ifEmpty { oldId }, // If the new update doesn't contain an ID keep our old value
|
||||
p.longName,
|
||||
p.shortName,
|
||||
p.hwModel
|
||||
p.hwModel,
|
||||
p.isLicensed
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1155,7 +1156,8 @@ class MeshService : Service(), Logging {
|
|||
info.user.id,
|
||||
info.user.longName,
|
||||
info.user.shortName,
|
||||
info.user.hwModel
|
||||
info.user.hwModel,
|
||||
info.user.isLicensed
|
||||
)
|
||||
|
||||
if (info.hasPosition()) {
|
||||
|
@ -1454,7 +1456,7 @@ class MeshService : Service(), Logging {
|
|||
/**
|
||||
* Set our owner with either the new or old API
|
||||
*/
|
||||
fun setOwner(myId: String?, longName: String, shortName: String) {
|
||||
fun setOwner(myId: String?, longName: String, shortName: String, isLicensed: Boolean) {
|
||||
val myNode = myNodeInfo
|
||||
if (myNode != null) {
|
||||
|
||||
|
@ -1468,6 +1470,7 @@ class MeshService : Service(), Logging {
|
|||
it.id = myId
|
||||
it.longName = longName
|
||||
it.shortName = shortName
|
||||
it.isLicensed = isLicensed
|
||||
}.build()
|
||||
|
||||
// Also update our own map for our nodenum, by handling the packet just like packets from other users
|
||||
|
@ -1608,9 +1611,9 @@ class MeshService : Service(), Logging {
|
|||
|
||||
override fun getMyId() = toRemoteExceptions { myNodeID }
|
||||
|
||||
override fun setOwner(myId: String?, longName: String, shortName: String) =
|
||||
override fun setOwner(myId: String?, longName: String, shortName: String, isLicensed: Boolean) =
|
||||
toRemoteExceptions {
|
||||
this@MeshService.setOwner(myId, longName, shortName)
|
||||
this@MeshService.setOwner(myId, longName, shortName, isLicensed)
|
||||
}
|
||||
|
||||
override fun send(p: DataPacket) {
|
||||
|
|
|
@ -4,25 +4,17 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.asLiveData
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hideKeyboard
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.databinding.AdvancedSettingsBinding
|
||||
import com.geeksville.mesh.model.ChannelOption
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.util.exceptionToSnackbar
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.composethemeadapter.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
|
||||
private val MAX_INT_DEVICE = 0xFFFFFFFF
|
||||
|
||||
private var _binding: AdvancedSettingsBinding? = null
|
||||
|
||||
private val binding get() = _binding!!
|
||||
|
@ -34,79 +26,18 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
|
|||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = AdvancedSettingsBinding.inflate(inflater, container, false)
|
||||
.apply {
|
||||
deviceConfig.apply {
|
||||
setViewCompositionStrategy(
|
||||
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
|
||||
)
|
||||
setContent {
|
||||
MdcTheme {
|
||||
PreferenceScreen(model)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
model.localConfig.asLiveData().observe(viewLifecycleOwner) {
|
||||
binding.positionBroadcastPeriodEditText.setText(model.config.position.positionBroadcastSecs.toString())
|
||||
binding.lsSleepEditText.setText(model.config.power.lsSecs.toString())
|
||||
binding.positionBroadcastPeriodView.isEnabled = model.config.position.gpsEnabled
|
||||
binding.positionBroadcastSwitch.isChecked = model.config.position.gpsEnabled
|
||||
binding.lsSleepView.isEnabled = model.config.power.isPowerSaving && model.isESP32()
|
||||
binding.lsSleepSwitch.isChecked = model.config.power.isPowerSaving && model.isESP32()
|
||||
}
|
||||
|
||||
model.connectionState.observe(viewLifecycleOwner) { connectionState ->
|
||||
val connected = connectionState == MeshService.ConnectionState.CONNECTED
|
||||
binding.positionBroadcastPeriodView.isEnabled = connected && model.config.position.gpsEnabled
|
||||
binding.lsSleepView.isEnabled = connected && model.config.power.isPowerSaving
|
||||
binding.positionBroadcastSwitch.isEnabled = connected
|
||||
binding.lsSleepSwitch.isEnabled = connected && model.isESP32()
|
||||
}
|
||||
|
||||
binding.positionBroadcastPeriodEditText.on(EditorInfo.IME_ACTION_DONE) {
|
||||
val textEdit = binding.positionBroadcastPeriodEditText
|
||||
val n = textEdit.text.toString().toIntOrNull()
|
||||
val minBroadcastPeriodSecs =
|
||||
ChannelOption.fromConfig(model.config.lora.modemPreset)?.minBroadcastPeriodSecs
|
||||
?: ChannelOption.defaultMinBroadcastPeriod
|
||||
|
||||
if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) {
|
||||
exceptionToSnackbar(requireView()) {
|
||||
model.updatePositionConfig { it.copy { positionBroadcastSecs = n } }
|
||||
}
|
||||
} else {
|
||||
// restore the value in the edit field
|
||||
textEdit.setText(model.config.position.positionBroadcastSecs.toString())
|
||||
val errorText =
|
||||
if (n == null || n < 0 || n >= MAX_INT_DEVICE)
|
||||
"Bad value: ${textEdit.text.toString()}"
|
||||
else
|
||||
getString(R.string.broadcast_period_too_small).format(minBroadcastPeriodSecs)
|
||||
|
||||
Snackbar.make(requireView(), errorText, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
requireActivity().hideKeyboard()
|
||||
}
|
||||
|
||||
binding.positionBroadcastSwitch.setOnCheckedChangeListener { btn, isChecked ->
|
||||
if (btn.isPressed) {
|
||||
model.updatePositionConfig { it.copy { gpsEnabled = isChecked } }
|
||||
debug("User changed locationShare to $isChecked")
|
||||
}
|
||||
}
|
||||
|
||||
binding.lsSleepEditText.on(EditorInfo.IME_ACTION_DONE) {
|
||||
val str = binding.lsSleepEditText.text.toString()
|
||||
val n = str.toIntOrNull()
|
||||
if (n != null && n < MAX_INT_DEVICE && n >= 0) {
|
||||
exceptionToSnackbar(requireView()) {
|
||||
model.updatePowerConfig { it.copy { lsSecs = n } }
|
||||
}
|
||||
} else {
|
||||
Snackbar.make(requireView(), "Bad value: $str", Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
requireActivity().hideKeyboard()
|
||||
}
|
||||
|
||||
binding.lsSleepSwitch.setOnCheckedChangeListener { btn, isChecked ->
|
||||
if (btn.isPressed) {
|
||||
model.updatePowerConfig { it.copy { isPowerSaving = isChecked } }
|
||||
debug("User changed isPowerSaving to $isChecked")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import com.geeksville.mesh.android.GeeksvilleApplication
|
|||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.hideKeyboard
|
||||
import com.geeksville.mesh.ChannelProtos
|
||||
import com.geeksville.mesh.ConfigKt.loRaConfig
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.getCameraPermissions
|
||||
|
|
|
@ -0,0 +1,807 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
import com.geeksville.mesh.ui.components.DropDownPreference
|
||||
import com.geeksville.mesh.ui.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.components.RegularPreference
|
||||
import com.geeksville.mesh.ui.components.SwitchPreference
|
||||
|
||||
private fun Int.uintToString(): String = this.toUInt().toString()
|
||||
private fun String.stringToIntOrNull(): Int? = this.toUIntOrNull()?.toInt()
|
||||
|
||||
@Composable
|
||||
fun PreferenceItemList(viewModel: UIViewModel) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
val hasWifi = viewModel.hasWifi()
|
||||
val connectionState = viewModel.connectionState.observeAsState()
|
||||
val connected = connectionState.value == MeshService.ConnectionState.CONNECTED
|
||||
|
||||
val localConfig by viewModel.localConfig.collectAsState()
|
||||
val user = viewModel.nodeDB.ourNodeInfo?.user
|
||||
|
||||
// Temporary [ConfigProtos.Config] state holders
|
||||
var userInput by remember { mutableStateOf(user) }
|
||||
var deviceInput by remember { mutableStateOf(localConfig.device) }
|
||||
var positionInput by remember { mutableStateOf(localConfig.position) }
|
||||
var powerInput by remember { mutableStateOf(localConfig.power) }
|
||||
var networkInput by remember { mutableStateOf(localConfig.network) }
|
||||
var displayInput by remember { mutableStateOf(localConfig.display) }
|
||||
var loraInput by remember { mutableStateOf(localConfig.lora) }
|
||||
var bluetoothInput by remember { mutableStateOf(localConfig.bluetooth) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item { PreferenceCategory(text = "User Config") }
|
||||
|
||||
item {
|
||||
RegularPreference(
|
||||
title = "Node ID",
|
||||
subtitle = userInput?.id ?: stringResource(id = R.string.unknown),
|
||||
onClick = {})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Long name",
|
||||
value = userInput?.longName ?: stringResource(id = R.string.unknown_username),
|
||||
enabled = connected && userInput?.longName != null,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
userInput?.let { userInput = it.copy(longName = value) }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Short name",
|
||||
value = userInput?.shortName ?: stringResource(id = R.string.unknown),
|
||||
enabled = connected && userInput?.shortName != null,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
userInput?.let { userInput = it.copy(shortName = value) }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
RegularPreference(
|
||||
title = "Hardware model",
|
||||
subtitle = userInput?.hwModel?.name ?: stringResource(id = R.string.unknown),
|
||||
onClick = {})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Licensed amateur radio",
|
||||
checked = userInput?.isLicensed ?: false,
|
||||
enabled = connected && userInput?.isLicensed != null,
|
||||
onCheckedChange = { value ->
|
||||
userInput?.let { userInput = it.copy(isLicensed = value) }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = userInput != user,
|
||||
onCancelClicked = { userInput = user },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
userInput?.let { viewModel.setOwner(it.longName, it.shortName, it.isLicensed) }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Device Config") }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Role",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.DeviceConfig.Role.values()
|
||||
.filter { it != ConfigProtos.Config.DeviceConfig.Role.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = deviceInput.role,
|
||||
onItemSelected = { deviceInput = deviceInput.copy { role = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Serial output enabled",
|
||||
checked = deviceInput.serialEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { serialEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Debug log enabled",
|
||||
checked = deviceInput.debugLogEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { debugLogEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = deviceInput != localConfig.device,
|
||||
onCancelClicked = { deviceInput = localConfig.device },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateDeviceConfig { deviceInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Position Config") }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Position broadcast interval",
|
||||
value = positionInput.positionBroadcastSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { positionInput = positionInput.copy { positionBroadcastSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Smart position enabled",
|
||||
checked = positionInput.positionBroadcastSmartEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = {
|
||||
positionInput = positionInput.copy { positionBroadcastSmartEnabled = it }
|
||||
})
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Use fixed position",
|
||||
checked = positionInput.fixedPosition,
|
||||
enabled = connected,
|
||||
onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "GPS enabled",
|
||||
checked = positionInput.gpsEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { positionInput = positionInput.copy { gpsEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "GPS update interval",
|
||||
value = positionInput.gpsUpdateInterval.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { positionInput = positionInput.copy { gpsUpdateInterval = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "Fix attempt duration",
|
||||
value = positionInput.gpsAttemptTime.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { positionInput = positionInput.copy { gpsAttemptTime = it } }
|
||||
})
|
||||
}
|
||||
|
||||
// TODO add positionFlags
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = positionInput != localConfig.position,
|
||||
onCancelClicked = { positionInput = localConfig.position },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updatePositionConfig { positionInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Power Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Enable power saving mode",
|
||||
checked = powerInput.isPowerSaving,
|
||||
enabled = connected && hasWifi, // We consider hasWifi = ESP32
|
||||
onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Shutdown on battery delay",
|
||||
value = powerInput.onBatteryShutdownAfterSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "ADC multiplier override ratio",
|
||||
value = powerInput.adcMultiplierOverride.toString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.toFloatOrNull()
|
||||
?.let { powerInput = powerInput.copy { adcMultiplierOverride = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Wait for Bluetooth duration",
|
||||
value = powerInput.waitBluetoothSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { waitBluetoothSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Mesh SDS timeout",
|
||||
value = powerInput.meshSdsTimeoutSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { meshSdsTimeoutSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Super deep sleep duration",
|
||||
value = powerInput.sdsSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { sdsSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Light sleep duration",
|
||||
value = powerInput.lsSecs.uintToString(),
|
||||
enabled = connected && hasWifi, // we consider hasWifi = ESP32
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { lsSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Minimum wake time",
|
||||
value = powerInput.minWakeSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { powerInput = powerInput.copy { minWakeSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = powerInput != localConfig.power,
|
||||
onCancelClicked = { powerInput = localConfig.power },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updatePowerConfig { powerInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Network Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "WiFi enabled",
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = connected && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "SSID",
|
||||
value = networkInput.wifiSsid.toString(),
|
||||
enabled = connected && hasWifi,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiSsid = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "PSK",
|
||||
value = networkInput.wifiPsk .toString(),
|
||||
enabled = connected && hasWifi,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Password, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "NTP server",
|
||||
value = networkInput.ntpServer.toString(),
|
||||
enabled = connected && hasWifi,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { networkInput = networkInput.copy { ntpServer = it } })
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "Ethernet enabled",
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Ethernet mode",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.NetworkConfig.EthMode.values()
|
||||
.filter { it != ConfigProtos.Config.NetworkConfig.EthMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = networkInput.ethMode,
|
||||
onItemSelected = { networkInput = networkInput.copy { ethMode = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item { PreferenceCategory(text = "IPv4 Config") }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "IP",
|
||||
value = networkInput.ipv4Config.ip.toString(),
|
||||
enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()?.let {
|
||||
networkInput = networkInput.copy { ipv4Config.copy { ip = it } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Gateway",
|
||||
value = networkInput.ipv4Config.gateway.toString(),
|
||||
enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()?.let {
|
||||
networkInput = networkInput.copy { ipv4Config.copy { gateway = it } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Subnet",
|
||||
value = networkInput.ipv4Config.subnet.toString(),
|
||||
enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()?.let {
|
||||
networkInput = networkInput.copy { ipv4Config.copy { subnet = it } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "DNS",
|
||||
value = networkInput.ipv4Config.dns.toString(),
|
||||
enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()?.let {
|
||||
networkInput = networkInput.copy { ipv4Config.copy { dns = it } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = networkInput != localConfig.network,
|
||||
onCancelClicked = { networkInput = localConfig.network },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateNetworkConfig { networkInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Display Config") }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Screen timeout",
|
||||
value = displayInput.screenOnSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { displayInput = displayInput.copy { screenOnSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "GPS coordinates format",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.values()
|
||||
.filter { it != ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.gpsFormat,
|
||||
onItemSelected = { displayInput = displayInput.copy { gpsFormat = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Auto screen carousel",
|
||||
value = displayInput.autoScreenCarouselSecs.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { displayInput = displayInput.copy { autoScreenCarouselSecs = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "Compass north top",
|
||||
checked = displayInput.compassNorthTop,
|
||||
enabled = connected,
|
||||
onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "Flip screen",
|
||||
checked = displayInput.flipScreen,
|
||||
enabled = connected,
|
||||
onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Display units",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.DisplayConfig.DisplayUnits.values()
|
||||
.filter { it != ConfigProtos.Config.DisplayConfig.DisplayUnits.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.units,
|
||||
onItemSelected = { displayInput = displayInput.copy { units = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
// item {
|
||||
// DropDownPreference(title = "Override OLED auto-detect",
|
||||
// enabled = connected,
|
||||
// items = ConfigProtos.Config.DisplayConfig.OledType.values()
|
||||
// .filter { it != ConfigProtos.Config.DisplayConfig.OledType.UNRECOGNIZED }
|
||||
// .map { it to it.name },
|
||||
// selectedItem = displayInput.oled,
|
||||
// onItemSelected = { displayInput = displayInput.copy { oled = it } })
|
||||
// }
|
||||
// item { Divider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = displayInput != localConfig.display,
|
||||
onCancelClicked = { displayInput = localConfig.display },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateDisplayConfig { displayInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "LoRa Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "Use modem preset",
|
||||
checked = loraInput.usePreset,
|
||||
enabled = connected,
|
||||
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Modem preset",
|
||||
enabled = connected && loraInput.usePreset,
|
||||
items = ConfigProtos.Config.LoRaConfig.ModemPreset.values()
|
||||
.filter { it != ConfigProtos.Config.LoRaConfig.ModemPreset.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = loraInput.modemPreset,
|
||||
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Bandwidth",
|
||||
value = loraInput.bandwidth.uintToString(),
|
||||
enabled = connected && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { bandwidth = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Spread factor",
|
||||
value = loraInput.spreadFactor.uintToString(),
|
||||
enabled = connected && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { spreadFactor = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Coding rate",
|
||||
value = loraInput.codingRate.uintToString(),
|
||||
enabled = connected && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { codingRate = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Frequency offset",
|
||||
value = loraInput.frequencyOffset.toString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.toFloatOrNull()
|
||||
?.let { loraInput = loraInput.copy { frequencyOffset = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Region (frequency plan)",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.LoRaConfig.RegionCode.values()
|
||||
.filter { it != ConfigProtos.Config.LoRaConfig.RegionCode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Hop limit",
|
||||
value = loraInput.hopLimit.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { hopLimit = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "TX enabled",
|
||||
checked = loraInput.txEnabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "TX power",
|
||||
value = loraInput.txPower.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { txPower = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Channel number",
|
||||
value = loraInput.channelNum.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { loraInput = loraInput.copy { channelNum = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = loraInput != localConfig.lora,
|
||||
onCancelClicked = { loraInput = localConfig.lora },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateLoraConfig { loraInput }
|
||||
})
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = "Bluetooth Config") }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = "Bluetooth enabled",
|
||||
checked = bluetoothInput.enabled,
|
||||
enabled = connected,
|
||||
onCheckedChange = { bluetoothInput = bluetoothInput.copy { enabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "Pairing mode",
|
||||
enabled = connected,
|
||||
items = ConfigProtos.Config.BluetoothConfig.PairingMode.values()
|
||||
.filter { it != ConfigProtos.Config.BluetoothConfig.PairingMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = bluetoothInput.mode,
|
||||
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "Fixed PIN",
|
||||
value = bluetoothInput.fixedPin.uintToString(),
|
||||
enabled = connected,
|
||||
keyboardActions = KeyboardActions(onSend = {
|
||||
focusManager.clearFocus()
|
||||
}),
|
||||
onValueChanged = { value ->
|
||||
value.stringToIntOrNull()
|
||||
?.let { bluetoothInput = bluetoothInput.copy { fixedPin = it } }
|
||||
})
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = bluetoothInput != localConfig.bluetooth,
|
||||
onCancelClicked = { bluetoothInput = localConfig.bluetooth },
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
viewModel.updateBluetoothConfig { bluetoothInput }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun PreferenceScreen(viewModel: UIViewModel = viewModel()) {
|
||||
PreferenceItemList(viewModel)
|
||||
}
|
||||
|
||||
//@Preview(showBackground = true)
|
||||
//@Composable
|
||||
//fun PreferencePreview() {
|
||||
// AppTheme {
|
||||
// PreferenceScreen(viewModel(factory = viewModelFactory { }))
|
||||
// }
|
||||
//}
|
|
@ -0,0 +1,86 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.MaterialTheme
|
||||
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.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun <T> DropDownPreference(
|
||||
title: String,
|
||||
enabled: Boolean,
|
||||
items: List<Pair<T, String>>,
|
||||
selectedItem: T,
|
||||
onItemSelected: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var dropDownExpanded by remember { mutableStateOf(value = false) }
|
||||
|
||||
RegularPreference(
|
||||
title = title,
|
||||
subtitle = items.first { it.first == selectedItem }.second,
|
||||
onClick = {
|
||||
dropDownExpanded = true
|
||||
},
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = if (dropDownExpanded)
|
||||
MaterialTheme.colors.primary.copy(alpha = 0.2f)
|
||||
else
|
||||
Color.Unspecified
|
||||
),
|
||||
enabled = enabled,
|
||||
)
|
||||
|
||||
Box {
|
||||
DropdownMenu(
|
||||
expanded = dropDownExpanded,
|
||||
onDismissRequest = { dropDownExpanded = !dropDownExpanded },
|
||||
) {
|
||||
items.forEach { item ->
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
dropDownExpanded = false
|
||||
onItemSelected(item.first)
|
||||
},
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = if (selectedItem == item.first)
|
||||
MaterialTheme.colors.primary.copy(alpha = 0.3f)
|
||||
else
|
||||
Color.Unspecified,
|
||||
),
|
||||
content = {
|
||||
Text(
|
||||
text = item.second,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DropDownPreferencePreview() {
|
||||
DropDownPreference(
|
||||
title = "Settings",
|
||||
enabled = true,
|
||||
items = listOf("TEST1" to "text1", "TEST2" to "text2"),
|
||||
selectedItem = "TEST2",
|
||||
onItemSelected = {}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable // Default keyboardOptions: KeyboardType.Number, ImeAction.Send
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: String,
|
||||
enabled: Boolean,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Send
|
||||
),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = onValueChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditTextPreference(
|
||||
title: String,
|
||||
value: String,
|
||||
enabled: Boolean,
|
||||
keyboardOptions: KeyboardOptions,
|
||||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
enabled = enabled,
|
||||
onValueChange = onValueChanged,
|
||||
label = { Text(title) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EditTextPreferencePreview() {
|
||||
EditTextPreference(
|
||||
title = "Advanced Settings",
|
||||
value = "${UInt.MAX_VALUE}",
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {}
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun PreferenceCategory(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Text(
|
||||
text,
|
||||
modifier = modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp, end = 16.dp),
|
||||
style = MaterialTheme.typography.h6,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreferenceCategoryPreview() {
|
||||
PreferenceCategory(
|
||||
text = "Advanced settings"
|
||||
)
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
@Composable
|
||||
fun PreferenceFooter(
|
||||
enabled: Boolean,
|
||||
onCancelClicked: () -> Unit,
|
||||
onSaveClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.size(48.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
enabled = enabled,
|
||||
onClick = onCancelClicked,
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.cancel),
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified,
|
||||
)
|
||||
}
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
enabled = enabled,
|
||||
onClick = onSaveClicked,
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Green)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.save_btn),
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.DarkGray,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreferenceFooterPreview() {
|
||||
PreferenceFooter(enabled = true, onCancelClicked = {}, onSaveClicked = {})
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun RegularPreference(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
RegularPreference(
|
||||
title = title,
|
||||
subtitle = AnnotatedString(text = subtitle),
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
enabled = enabled
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RegularPreference(
|
||||
title: String,
|
||||
subtitle: AnnotatedString,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
enabled = enabled,
|
||||
onClick = onClick,
|
||||
)
|
||||
.padding(all = 16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RegularPreferencePreview() {
|
||||
RegularPreference(
|
||||
title = "Advanced settings",
|
||||
subtitle = AnnotatedString(text = "Lorem ipsum dolor sit amet"),
|
||||
onClick = { },
|
||||
)
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
|
||||
@Composable
|
||||
fun SwitchPreference(
|
||||
title: String,
|
||||
checked: Boolean,
|
||||
enabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.size(48.dp)
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified,
|
||||
)
|
||||
Switch(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.End),
|
||||
enabled = enabled,
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
colors = SwitchDefaults.colors(
|
||||
uncheckedThumbColor = colorResource(R.color.colourGrey)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SwitchPreferencePreview() {
|
||||
SwitchPreference(title = "Setting", checked = true, enabled = true, onCheckedChange = {})
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package com.geeksville.mesh.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple200 = Color(0xFFBB86FC)
|
||||
val Purple500 = Color(0xFF6200EE)
|
||||
val Purple700 = Color(0xFF3700B3)
|
||||
val Teal200 = Color(0xFF03DAC5)
|
||||
|
||||
val LightGray = Color(0xFFFAFAFA)
|
||||
val LightSkyBlue = Color(0x99A6D1E6)
|
||||
val LightBlue = Color(0xFFA6D1E6)
|
||||
val SkyBlue = Color(0xFF57AEFF)
|
||||
val LightPink = Color(0xFFFFE6E6)
|
||||
val LightGreen = Color(0xFFCFE8A9)
|
||||
val LightRed = Color(0xFFFFB3B3)
|
||||
|
||||
val MeshtasticGreen = Color(0xFF67EA94)
|
|
@ -0,0 +1,11 @@
|
|||
package com.geeksville.mesh.ui.theme
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp)
|
||||
)
|
|
@ -0,0 +1,47 @@
|
|||
package com.geeksville.mesh.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
private val DarkColorPalette = darkColors(
|
||||
primary = Purple200,
|
||||
primaryVariant = Purple700,
|
||||
secondary = Teal200
|
||||
)
|
||||
|
||||
private val LightColorPalette = lightColors(
|
||||
primary = SkyBlue,
|
||||
primaryVariant = LightSkyBlue,
|
||||
secondary = Teal200
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.Black,
|
||||
onBackground = Color.Black,
|
||||
onSurface = Color.Black,
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AppTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colors = if (darkTheme) {
|
||||
DarkColorPalette
|
||||
} else {
|
||||
LightColorPalette
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colors = colors,
|
||||
typography = Typography,
|
||||
shapes = Shapes,
|
||||
content = content
|
||||
)
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package com.geeksville.mesh.ui.theme
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
h3 = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 24.sp
|
||||
),
|
||||
h4 = TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp
|
||||
),
|
||||
body1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
body2 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
/* Other default text styles to override
|
||||
button = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.W500,
|
||||
fontSize = 14.sp
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
*/
|
||||
)
|
|
@ -6,64 +6,10 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@color/colorAdvancedBackground">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/positionBroadcastPeriodView"
|
||||
android:layout_width="0dp"
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/device_config"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/broadcast_position_secs"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/positionBroadcastPeriodEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/positionBroadcastSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/positionBroadcastPeriodView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/positionBroadcastPeriodView" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/lsSleepView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:hint="@string/ls_sleep_secs"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/positionBroadcastPeriodView">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/lsSleepEditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:singleLine="true" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/lsSleepSwitch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/lsSleepView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/lsSleepView" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -73,10 +73,8 @@
|
|||
<string name="message_reception_time">tiempo de recepción del mensaje</string>
|
||||
<string name="message_reception_state">estado de recepción de mensajes</string>
|
||||
<string name="message_delivery_status">Estado de entrega del mensaje</string>
|
||||
<string name="broadcast_position_secs">Periodo de emisión de la posición (en segundos)</string>
|
||||
<string name="ls_sleep_secs">Período de reposo del dispositivo (en segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificaciones de mensajes</string>
|
||||
<string name="broadcast_period_too_small">El periodo mínimo de emisión de este canal es %d</string>
|
||||
<string name="protocol_stress_test">Protocolo de prueba de esfuerzo</string>
|
||||
<string name="advanced_settings">Configuración avanzada</string>
|
||||
<string name="firmware_too_old">Es necesario actualizar el firmware</string>
|
||||
|
|
|
@ -73,10 +73,8 @@
|
|||
<string name="message_reception_time">üzenet fogadásának ideje</string>
|
||||
<string name="message_reception_state">üzenet fogadásának állapota</string>
|
||||
<string name="message_delivery_status">Üzenet kézbesítésének állapota</string>
|
||||
<string name="broadcast_position_secs">Pozíció hírdetésének gyakorisága (másodpercben)</string>
|
||||
<string name="ls_sleep_secs">Eszköz alvásának gyakorisága (másodpercben)</string>
|
||||
<string name="meshtastic_messages_notifications">Értesítések az üzenetekről</string>
|
||||
<string name="broadcast_period_too_small">Minimum üzenet küldési gyakoriság ezen a csatornán %d</string>
|
||||
<string name="protocol_stress_test">Protokoll stressz teszt</string>
|
||||
<string name="advanced_settings">Haladó beállítások</string>
|
||||
<string name="firmware_too_old">Firmware frissítés szükséges</string>
|
||||
|
|
|
@ -79,10 +79,8 @@
|
|||
<string name="message_reception_time">메시지 수신 시간</string>
|
||||
<string name="message_reception_state">메시지 수신 상태</string>
|
||||
<string name="message_delivery_status">메시지 전송 상태</string>
|
||||
<string name="broadcast_position_secs">위치 전송 주기 (in seconds)</string>
|
||||
<string name="ls_sleep_secs">기기가 절전모드 들어가기까지의 시간 (in seconds)</string>
|
||||
<string name="meshtastic_messages_notifications">메시지 알림</string>
|
||||
<string name="broadcast_period_too_small">이 채널의 최소 전송 주기는 %d 입니다.</string>
|
||||
<string name="protocol_stress_test">프로토콜 스트레스 테스트</string>
|
||||
<string name="advanced_settings">고급 설정</string>
|
||||
<string name="firmware_too_old">펌웨어 업데이트 필요</string>
|
||||
|
|
|
@ -74,10 +74,8 @@
|
|||
<string name="message_reception_time">czas odbioru wiadomości</string>
|
||||
<string name="message_reception_state">stan odbioru wiadomości</string>
|
||||
<string name="message_delivery_status">Status doręczenia wiadomości</string>
|
||||
<string name="broadcast_position_secs">Okres pozycji transmisji (w sekundach)</string>
|
||||
<string name="ls_sleep_secs">Okres uśpienia urządzenia (w sekundach)</string>
|
||||
<string name="meshtastic_messages_notifications">Powiadomienia o wiadomościach</string>
|
||||
<string name="broadcast_period_too_small">Minimalny okres nadawania dla tego kanału to %d</string>
|
||||
<string name="protocol_stress_test">Protokół testu warunków skrajnych</string>
|
||||
<string name="advanced_settings">Zaawansowane ustawienia</string>
|
||||
<string name="firmware_too_old">Wymagana aktualizacja oprogramowania układowego</string>
|
||||
|
|
|
@ -74,10 +74,8 @@
|
|||
<string name="message_reception_time">tempo de recebimento de mensagem</string>
|
||||
<string name="message_reception_state">estado de recebimento de mensagem</string>
|
||||
<string name="message_delivery_status">Status de entrega de mensagem</string>
|
||||
<string name="broadcast_position_secs">Intervalo de localização (em segundos)</string>
|
||||
<string name="ls_sleep_secs">Intervalo de suspensão (sleep) (em segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificações sobre mensagens</string>
|
||||
<string name="broadcast_period_too_small">Período mínimo de transmissão para este canal é %d</string>
|
||||
<string name="protocol_stress_test">Stress test do protocolo</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="firmware_too_old">Atualização do firmware necessária</string>
|
||||
|
|
|
@ -74,10 +74,8 @@
|
|||
<string name="message_reception_time">tempo de recebimento de mensagem</string>
|
||||
<string name="message_reception_state">estado de recebimento de mensagem</string>
|
||||
<string name="message_delivery_status">Status de entrega de mensagem</string>
|
||||
<string name="broadcast_position_secs">Intervalo de localização (segundos)</string>
|
||||
<string name="ls_sleep_secs">Intervalo de suspensão (sleep) (segundos)</string>
|
||||
<string name="meshtastic_messages_notifications">Notificações sobre mensagens</string>
|
||||
<string name="broadcast_period_too_small">Período mínimo de transmissão para este canal é %d</string>
|
||||
<string name="protocol_stress_test">Stress test do protocolo</string>
|
||||
<string name="advanced_settings">Configurações avançadas</string>
|
||||
<string name="firmware_too_old">Atualização do firmware necessária</string>
|
||||
|
|
|
@ -74,10 +74,8 @@
|
|||
<string name="message_reception_time">čas prijatia správy</string>
|
||||
<string name="message_reception_state">stav prijatia správy</string>
|
||||
<string name="message_delivery_status">stav doručenia správy</string>
|
||||
<string name="broadcast_position_secs">Interval rozosielania pozície (v sekundách)</string>
|
||||
<string name="ls_sleep_secs">Interval uspávania zariadenia (v sekundách)</string>
|
||||
<string name="meshtastic_messages_notifications">Upozornenia na správy</string>
|
||||
<string name="broadcast_period_too_small">Najkratší interval rozosielania pre tento kanál je %d</string>
|
||||
<string name="protocol_stress_test">Stres test protokolu</string>
|
||||
<string name="advanced_settings">Rozšírené nastavenia</string>
|
||||
<string name="firmware_too_old">Nutná aktualizácia firmvéru vysielača</string>
|
||||
|
|
|
@ -74,10 +74,8 @@
|
|||
<string name="message_reception_time">消息接收时间</string>
|
||||
<string name="message_reception_state">消息接收状态</string>
|
||||
<string name="message_delivery_status">消息传递状态</string>
|
||||
<string name="broadcast_position_secs">广播周期(以秒为单位)</string>
|
||||
<string name="ls_sleep_secs">设备休眠时间(以秒为单位)</string>
|
||||
<string name="meshtastic_messages_notifications">关于消息的通知</string>
|
||||
<string name="broadcast_period_too_small">此频道的最短广播时间为 %d</string>
|
||||
<string name="protocol_stress_test">协议压力测试</string>
|
||||
<string name="advanced_settings">高级设置</string>
|
||||
<string name="firmware_too_old">需要固件更新</string>
|
||||
|
|
|
@ -78,10 +78,8 @@
|
|||
<string name="message_reception_time">message reception time</string>
|
||||
<string name="message_reception_state">message reception state</string>
|
||||
<string name="message_delivery_status">Message delivery status</string>
|
||||
<string name="broadcast_position_secs">Broadcast position period (in seconds)</string>
|
||||
<string name="ls_sleep_secs">Device sleep period (in seconds)</string>
|
||||
<string name="meshtastic_messages_notifications">Notifications about messages</string>
|
||||
<string name="broadcast_period_too_small">Minimum broadcast period for this channel is %d</string>
|
||||
<string name="protocol_stress_test">Protocol stress test</string>
|
||||
<string name="advanced_settings">Advanced settings</string>
|
||||
<string name="firmware_too_old">Firmware update required</string>
|
||||
|
|
Ładowanie…
Reference in New Issue