add user & device config settings (#520)

* add MeshUser & LocalConfig prefs
pull/522/head
Andre K 2022-11-08 23:11:18 -03:00 zatwierdzone przez GitHub
rodzic 17dc4da191
commit 4bcd408dce
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
30 zmienionych plików z 1383 dodań i 200 usunięć

Wyświetl plik

@ -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();

Wyświetl plik

@ -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

Wyświetl plik

@ -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
}
}

Wyświetl plik

@ -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
)

Wyświetl plik

@ -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}")

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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")
}
}
}
}
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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 }
})
}
}
}

Wyświetl plik

@ -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 { }))
// }
//}

Wyświetl plik

@ -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 = {}
)
}

Wyświetl plik

@ -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 = {}
)
}

Wyświetl plik

@ -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"
)
}

Wyświetl plik

@ -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 = {})
}

Wyświetl plik

@ -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 = { },
)
}

Wyświetl plik

@ -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 = {})
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)
)

Wyświetl plik

@ -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
)
}

Wyświetl plik

@ -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
)
*/
)

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>