refactor config get and set methods

This commit is contained in:
andrekir 2022-09-12 00:26:12 -03:00
parent 346a50a360
commit fd0c8ef9b8
4 changed files with 72 additions and 118 deletions

View file

@ -13,10 +13,12 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.* import com.geeksville.mesh.*
import com.geeksville.mesh.ConfigProtos.Config
import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.PacketRepository
import com.geeksville.mesh.database.QuickChatActionRepository import com.geeksville.mesh.database.QuickChatActionRepository
import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.Packet
import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.LocalOnlyProtos.LocalConfig
import com.geeksville.mesh.repository.datastore.LocalConfigRepository import com.geeksville.mesh.repository.datastore.LocalConfigRepository
import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.util.GPSFormat import com.geeksville.mesh.util.GPSFormat
@ -71,8 +73,9 @@ class UIViewModel @Inject constructor(
private val _allPacketState = MutableStateFlow<List<Packet>>(emptyList()) private val _allPacketState = MutableStateFlow<List<Packet>>(emptyList())
val allPackets: StateFlow<List<Packet>> = _allPacketState val allPackets: StateFlow<List<Packet>> = _allPacketState
private val _localConfig = MutableLiveData<LocalOnlyProtos.LocalConfig>() private val _localConfig = MutableStateFlow<LocalConfig>(LocalConfig.getDefaultInstance())
val localConfig: LiveData<LocalOnlyProtos.LocalConfig> get() = _localConfig val localConfig: StateFlow<LocalConfig> = _localConfig
val config get() = _localConfig.value
private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList()) private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList())
val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions
@ -139,80 +142,10 @@ class UIViewModel @Inject constructor(
_requestChannelUrl.value = null _requestChannelUrl.value = null
} }
var positionBroadcastSecs: Int? var region: Config.LoRaConfig.RegionCode
get() { get() = config.lora?.region ?: Config.LoRaConfig.RegionCode.Unset
_localConfig.value?.position?.positionBroadcastSecs?.let {
return if (it > 0) it else defaultPositionBroadcastSecs
}
return null
}
set(value) { set(value) {
val config = _localConfig.value updateLoraConfig { it.copy { region = value } }
if (value != null && config != null) {
val builder = config.position.toBuilder()
builder.positionBroadcastSecs =
if (value == defaultPositionBroadcastSecs) 0 else value
val newConfig = ConfigProtos.Config.newBuilder()
newConfig.position = builder.build()
setDeviceConfig(newConfig.build())
}
}
var lsSleepSecs: Int?
get() {
_localConfig.value?.power?.lsSecs?.let {
return if (it > 0) it else defaultLsSecs
}
return null
}
set(value) {
val config = _localConfig.value
if (value != null && config != null) {
val builder = config.power.toBuilder()
builder.lsSecs = if (value == defaultLsSecs) 0 else value
val newConfig = ConfigProtos.Config.newBuilder()
newConfig.power = builder.build()
setDeviceConfig(newConfig.build())
}
}
var gpsDisabled: Boolean
get() = _localConfig.value?.position?.gpsDisabled ?: false
set(value) {
val config = _localConfig.value
if (config != null) {
val builder = config.position.toBuilder()
builder.gpsDisabled = value
val newConfig = ConfigProtos.Config.newBuilder()
newConfig.position = builder.build()
setDeviceConfig(newConfig.build())
}
}
var isPowerSaving: Boolean?
get() = _localConfig.value?.power?.isPowerSaving
set(value) {
val config = _localConfig.value
if (value != null && config != null) {
val builder = config.power.toBuilder()
builder.isPowerSaving = value
val newConfig = ConfigProtos.Config.newBuilder()
newConfig.power = builder.build()
setDeviceConfig(newConfig.build())
}
}
var region: ConfigProtos.Config.LoRaConfig.RegionCode
get() = localConfig.value?.lora?.region ?: ConfigProtos.Config.LoRaConfig.RegionCode.Unset
set(value) {
val config = _localConfig.value
if (config != null) {
val builder = config.lora.toBuilder()
builder.region = value
val newConfig = ConfigProtos.Config.newBuilder()
newConfig.lora = builder.build()
setDeviceConfig(newConfig.build())
}
} }
fun gpsString(pos: Position): String { fun gpsString(pos: Position): String {
@ -226,12 +159,7 @@ class UIViewModel @Inject constructor(
} }
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
val isRouter: Boolean = val isRouter: Boolean = config.device?.role == Config.DeviceConfig.Role.Router
localConfig.value?.device?.role == ConfigProtos.Config.DeviceConfig.Role.Router
// These default values are borrowed from the device code.
private val defaultPositionBroadcastSecs = if (isRouter) 12 * 60 * 60 else 15 * 60
private val defaultLsSecs = if (isRouter) 24 * 60 * 60 else 5 * 60
// We consider hasWifi = ESP32 // We consider hasWifi = ESP32
fun isESP32() = myNodeInfo.value?.hasWifi == true fun isESP32() = myNodeInfo.value?.hasWifi == true
@ -272,19 +200,44 @@ class UIViewModel @Inject constructor(
} }
} }
/** inline fun updateDeviceConfig(crossinline body: (Config.DeviceConfig) -> Config.DeviceConfig) {
* Return the primary channel info val data = body(config.device)
*/ setDeviceConfig(config { device = data })
val primaryChannel: Channel? get() = _channels.value?.primaryChannel
// Set the radio config (also updates our saved copy in preferences)
private fun setDeviceConfig(config: ConfigProtos.Config) {
meshService?.deviceConfig = config.toByteArray()
} }
fun setLocalConfig(localConfig: LocalOnlyProtos.LocalConfig) { inline fun updatePositionConfig(crossinline body: (Config.PositionConfig) -> Config.PositionConfig) {
if (_localConfig.value == localConfig) return val data = body(config.position)
_localConfig.value = localConfig setDeviceConfig(config { position = data })
}
inline fun updatePowerConfig(crossinline body: (Config.PowerConfig) -> Config.PowerConfig) {
val data = body(config.power)
setDeviceConfig(config { power = data })
}
inline fun updateNetworkConfig(crossinline body: (Config.WiFiConfig) -> Config.WiFiConfig) {
val data = body(config.wifi)
setDeviceConfig(config { wifi = data })
}
inline fun updateDisplayConfig(crossinline body: (Config.DisplayConfig) -> Config.DisplayConfig) {
val data = body(config.display)
setDeviceConfig(config { display = data })
}
inline fun updateLoraConfig(crossinline body: (Config.LoRaConfig) -> Config.LoRaConfig) {
val data = body(config.lora)
setDeviceConfig(config { lora = data })
}
inline fun updateBluetoothConfig(crossinline body: (Config.BluetoothConfig) -> Config.BluetoothConfig) {
val data = body(config.bluetooth)
setDeviceConfig(config { bluetooth = data })
}
// Set the radio config (also updates our saved copy in preferences)
fun setDeviceConfig(config: Config) {
meshService?.deviceConfig = config.toByteArray()
} }
/// Set the radio config (also updates our saved copy in preferences) /// Set the radio config (also updates our saved copy in preferences)

View file

@ -493,10 +493,8 @@ class MeshService : Service(), Logging {
setChannel(it) setChannel(it)
} }
val newConfig = ConfigProtos.Config.newBuilder() val newConfig = config { lora = value.loraConfig }
val newPrefs = (value.loraConfig).toBuilder() if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig)
newConfig.lora = newPrefs.build()
if (localConfig.lora != newConfig.lora) sendDeviceConfig(newConfig.build())
channels = fixupChannelList(asChannels) channels = fixupChannelList(asChannels)
} }

View file

@ -6,9 +6,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.asLiveData
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.hideKeyboard import com.geeksville.mesh.android.hideKeyboard
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.copy
import com.geeksville.mesh.databinding.AdvancedSettingsBinding import com.geeksville.mesh.databinding.AdvancedSettingsBinding
import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.ChannelOption
import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.UIViewModel
@ -38,19 +40,19 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
model.localConfig.observe(viewLifecycleOwner) { model.localConfig.asLiveData().observe(viewLifecycleOwner) {
binding.positionBroadcastPeriodEditText.setText(model.positionBroadcastSecs.toString()) binding.positionBroadcastPeriodEditText.setText(model.config.position.positionBroadcastSecs.toString())
binding.lsSleepEditText.setText(model.lsSleepSecs.toString()) binding.lsSleepEditText.setText(model.config.power.lsSecs.toString())
binding.positionBroadcastPeriodView.isEnabled = !model.gpsDisabled binding.positionBroadcastPeriodView.isEnabled = !model.config.position.gpsDisabled
binding.positionBroadcastSwitch.isChecked = !model.gpsDisabled binding.positionBroadcastSwitch.isChecked = !model.config.position.gpsDisabled
binding.lsSleepView.isEnabled = model.isPowerSaving ?: false && model.isESP32() binding.lsSleepView.isEnabled = model.config.power.isPowerSaving && model.isESP32()
binding.lsSleepSwitch.isChecked = model.isPowerSaving ?: false && model.isESP32() binding.lsSleepSwitch.isChecked = model.config.power.isPowerSaving && model.isESP32()
} }
model.connectionState.observe(viewLifecycleOwner) { connectionState -> model.connectionState.observe(viewLifecycleOwner) { connectionState ->
val connected = connectionState == MeshService.ConnectionState.CONNECTED val connected = connectionState == MeshService.ConnectionState.CONNECTED
binding.positionBroadcastPeriodView.isEnabled = connected && !model.gpsDisabled binding.positionBroadcastPeriodView.isEnabled = connected && !model.config.position.gpsDisabled
binding.lsSleepView.isEnabled = connected && model.isPowerSaving ?: false binding.lsSleepView.isEnabled = connected && model.config.power.isPowerSaving
binding.positionBroadcastSwitch.isEnabled = connected binding.positionBroadcastSwitch.isEnabled = connected
binding.lsSleepSwitch.isEnabled = connected && model.isESP32() binding.lsSleepSwitch.isEnabled = connected && model.isESP32()
binding.shutdownButton.isEnabled = connected && model.hasAXP() binding.shutdownButton.isEnabled = connected && model.hasAXP()
@ -62,16 +64,16 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
val textEdit = binding.positionBroadcastPeriodEditText val textEdit = binding.positionBroadcastPeriodEditText
val n = textEdit.text.toString().toIntOrNull() val n = textEdit.text.toString().toIntOrNull()
val minBroadcastPeriodSecs = val minBroadcastPeriodSecs =
ChannelOption.fromConfig(model.localConfig.value?.lora?.modemPreset)?.minBroadcastPeriodSecs ChannelOption.fromConfig(model.config.lora.modemPreset)?.minBroadcastPeriodSecs
?: ChannelOption.defaultMinBroadcastPeriod ?: ChannelOption.defaultMinBroadcastPeriod
if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) { if (n != null && n < MAX_INT_DEVICE && (n == 0 || n >= minBroadcastPeriodSecs)) {
exceptionToSnackbar(requireView()) { exceptionToSnackbar(requireView()) {
model.positionBroadcastSecs = n model.updatePositionConfig { it.copy { positionBroadcastSecs = n } }
} }
} else { } else {
// restore the value in the edit field // restore the value in the edit field
textEdit.setText(model.positionBroadcastSecs.toString()) textEdit.setText(model.config.position.positionBroadcastSecs.toString())
val errorText = val errorText =
if (n == null || n < 0 || n >= MAX_INT_DEVICE) if (n == null || n < 0 || n >= MAX_INT_DEVICE)
"Bad value: ${textEdit.text.toString()}" "Bad value: ${textEdit.text.toString()}"
@ -83,9 +85,9 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
requireActivity().hideKeyboard() requireActivity().hideKeyboard()
} }
binding.positionBroadcastSwitch.setOnCheckedChangeListener { view, isChecked -> binding.positionBroadcastSwitch.setOnCheckedChangeListener { btn, isChecked ->
if (view.isPressed) { if (btn.isPressed) {
model.gpsDisabled = !isChecked model.updatePositionConfig { it.copy { gpsDisabled = !isChecked } }
debug("User changed locationShare to $isChecked") debug("User changed locationShare to $isChecked")
} }
} }
@ -95,7 +97,7 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
val n = str.toIntOrNull() val n = str.toIntOrNull()
if (n != null && n < MAX_INT_DEVICE && n >= 0) { if (n != null && n < MAX_INT_DEVICE && n >= 0) {
exceptionToSnackbar(requireView()) { exceptionToSnackbar(requireView()) {
model.lsSleepSecs = n model.updatePowerConfig { it.copy { lsSecs = n } }
} }
} else { } else {
Snackbar.make(requireView(), "Bad value: $str", Snackbar.LENGTH_LONG).show() Snackbar.make(requireView(), "Bad value: $str", Snackbar.LENGTH_LONG).show()
@ -103,9 +105,9 @@ class AdvancedSettingsFragment : ScreenFragment("Advanced Settings"), Logging {
requireActivity().hideKeyboard() requireActivity().hideKeyboard()
} }
binding.lsSleepSwitch.setOnCheckedChangeListener { view, isChecked -> binding.lsSleepSwitch.setOnCheckedChangeListener { btn, isChecked ->
if (view.isPressed) { if (btn.isPressed) {
model.isPowerSaving = isChecked model.updatePowerConfig { it.copy { isPowerSaving = isChecked } }
debug("User changed isPowerSaving to $isChecked") debug("User changed isPowerSaving to $isChecked")
} }
} }

View file

@ -13,6 +13,7 @@ import android.widget.*
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.asLiveData
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.geeksville.mesh.analytics.DataPair import com.geeksville.mesh.analytics.DataPair
@ -160,9 +161,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (connected == MeshService.ConnectionState.DISCONNECTED) if (connected == MeshService.ConnectionState.DISCONNECTED)
model.setOwner("") model.setOwner("")
if (model.gpsDisabled) { if (model.config.position.gpsDisabled) {
model.provideLocation.value = false
binding.provideLocationCheckbox.isChecked = false binding.provideLocationCheckbox.isChecked = false
binding.provideLocationCheckbox.isEnabled = false
} else { } else {
binding.provideLocationCheckbox.isEnabled = true binding.provideLocationCheckbox.isEnabled = true
} }
@ -287,7 +288,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
updateDevicesButtons(scanModel.devices.value) updateDevicesButtons(scanModel.devices.value)
} }
model.localConfig.observe(viewLifecycleOwner) { model.localConfig.asLiveData().observe(viewLifecycleOwner) {
if (!model.isConnected()) { if (!model.isConnected()) {
val configCount = it.allFields.size val configCount = it.allFields.size
binding.scanStatusText.text = "Device config ($configCount / 7)" binding.scanStatusText.text = "Device config ($configCount / 7)"