Merge pull request #266 from geeksville/dev1.2

Dev1.2
pull/267/head^2 1.2.13
Kevin Hester 2021-03-24 13:53:05 +08:00 zatwierdzone przez GitHub
commit b44a104be2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
15 zmienionych plików z 246 dodań i 154 usunięć

Wyświetl plik

@ -30,15 +30,15 @@ android {
keyPassword "$meshtasticKeyPassword"
}
} */
compileSdkVersion 29
compileSdkVersion 30
// leave undefined to use version plugin wants
// buildToolsVersion "30.0.2" // Note: 30.0.2 doesn't yet work on Github actions CI
defaultConfig {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
targetSdkVersion 29
versionCode 20211 // format is Mmmss (where M is 1+the numeric major number
versionName "1.2.11"
targetSdkVersion 30
versionCode 20213 // format is Mmmss (where M is 1+the numeric major number
versionName "1.2.13"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// per https://developer.android.com/studio/write/vector-asset-studio
@ -151,7 +151,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
// For now I'm not using javalite, because I want JSON printing
implementation ('com.google.protobuf:protobuf-java:3.15.5')
implementation ('com.google.protobuf:protobuf-java:3.15.6')
// For UART access
// implementation 'com.google.android.things:androidthings:1.0'
@ -170,7 +170,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-auth:19.0.0'
// Add the Firebase SDK for Crashlytics.
implementation 'com.google.firebase:firebase-crashlytics:17.3.1'
implementation 'com.google.firebase:firebase-crashlytics:17.4.0'
// alas implementation bug deep in the bowels when I tried it for my SyncBluetoothDevice class
// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"

Wyświetl plik

@ -113,4 +113,7 @@ interface IMeshService {
Return a number 0-100 for progress. -1 for completed and success, -2 for failure
*/
int getUpdateStatus();
int getRegion();
void setRegion(int regionCode);
}

Wyświetl plik

@ -663,30 +663,32 @@ class MainActivity : AppCompatActivity(), Logging,
debug("Getting latest radioconfig from service")
try {
val info = service.myNodeInfo
val info: MyNodeInfo? = service.myNodeInfo // this can be null
model.myNodeInfo.value = info
val isOld = info.minAppVersion > BuildConfig.VERSION_CODE
if (isOld)
showAlert(R.string.app_too_old, R.string.must_update)
else {
val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0")
if (curVer < MeshService.minFirmwareVersion)
showAlert(R.string.firmware_too_old, R.string.firmware_old)
if (info != null) {
val isOld = info.minAppVersion > BuildConfig.VERSION_CODE
if (isOld)
showAlert(R.string.app_too_old, R.string.must_update)
else {
// If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here
model.radioConfig.value =
RadioConfigProtos.RadioConfig.parseFrom(service.radioConfig)
val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0")
if (curVer < MeshService.minFirmwareVersion)
showAlert(R.string.firmware_too_old, R.string.firmware_old)
else {
// If our app is too old/new, we probably don't understand the new radioconfig messages, so we don't read them until here
model.channels.value =
ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels))
model.radioConfig.value =
RadioConfigProtos.RadioConfig.parseFrom(service.radioConfig)
updateNodesFromDevice()
model.channels.value =
ChannelSet(AppOnlyProtos.ChannelSet.parseFrom(service.channels))
// we have a connection to our device now, do the channel change
perhapsChangeChannel()
updateNodesFromDevice()
// we have a connection to our device now, do the channel change
perhapsChangeChannel()
}
}
}
} catch (ex: RemoteException) {
@ -972,12 +974,11 @@ class MainActivity : AppCompatActivity(), Logging,
try {
bindMeshService()
}
catch(ex: BindFailedException) {
} catch (ex: BindFailedException) {
// App is probably shutting down, ignore
errormsg("Bind of MeshService failed")
}
val bonded = RadioInterfaceService.getBondedDeviceAddress(this) != null
if (!bonded && usbDevice == null) // we will handle USB later
showSettingsPage()

Wyświetl plik

@ -17,10 +17,15 @@ data class Channel(
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf
)
private val cleartextPSK = ByteString.EMPTY
private val defaultPSK = byteArrayOfInts(1) // a shortstring code to indicate we need our default PSK
// TH=he unsecured channel that devices ship with
val defaultChannel = Channel(
ChannelProtos.ChannelSettings.newBuilder()
.setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.Bw125Cr45Sf128).build()
.setModemConfig(ChannelProtos.ChannelSettings.ModemConfig.Bw125Cr48Sf4096)
.setPsk(ByteString.copyFrom(defaultPSK))
.build()
)
}
@ -50,7 +55,7 @@ data class Channel(
val pskIndex = settings.psk.byteAt(0).toInt()
if (pskIndex == 0)
ByteString.EMPTY // Treat as an empty PSK (no encryption)
cleartextPSK
else {
// Treat an index of 1 as the old channelDefaultKey and work up from there
val bytes = channelDefaultKey.clone()

Wyświetl plik

@ -134,15 +134,10 @@ class UIViewModel(private val app: Application) : AndroidViewModel(app), Logging
}
}
var region: RadioConfigProtos.RegionCode?
get() = radioConfig.value?.preferences?.region
var region: RadioConfigProtos.RegionCode
get() = meshService?.region?.let { RadioConfigProtos.RegionCode.forNumber(it) } ?: RadioConfigProtos.RegionCode.Unset
set(value) {
val config = radioConfig.value
if (value != null && config != null) {
val builder = config.toBuilder()
builder.preferencesBuilder.region = value
setRadioConfig(builder.build())
}
meshService?.region = value.number
}
/// hardware info about our local device (can be null)

Wyświetl plik

@ -5,4 +5,7 @@ import java.util.*
open class BLEException(msg: String) : IOException(msg)
open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid")
open class BLECharacteristicNotFoundException(uuid: UUID) : BLEException("Can't get characteristic $uuid")
/// Our interface is being shut down
open class BLEConnectionClosing() : BLEException("Connection closing ")

Wyświetl plik

@ -313,7 +313,7 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
var fromNumChanged = false
private fun startWatchingFromNum() {
safe!!.setNotify(fromNum, true) {
safe?.setNotify(fromNum, true) {
// We might get multiple notifies before we get around to reading from the radio - so just set one flag
fromNumChanged = true
debug("fromNum changed")
@ -469,7 +469,12 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
safe =
null // We do this first, because if we throw we still want to mark that we no longer have a valid connection
s?.close()
try {
s?.close()
}
catch(_: BLEConnectionClosing) {
warn("Ignoring BLE errors while closing")
}
} else {
debug("Radio was not connected, skipping disable")
}

Wyświetl plik

@ -55,7 +55,7 @@ class MeshService : Service(), Logging {
/* @Deprecated(message = "Does not filter by port number. For legacy reasons only broadcast for UNKNOWN_APP, switch to ACTION_RECEIVED")
const val ACTION_RECEIVED_DATA = "$prefix.RECEIVED_DATA" */
fun actionReceived(portNum: String) = "$prefix.RECEIVED.$portNum"
private fun actionReceived(portNum: String) = "$prefix.RECEIVED.$portNum"
/// generate a RECEIVED action filter string that includes either the portnumber as an int, or preferably a symbolic name from portnums.proto
fun actionReceived(portNum: Int): String {
@ -549,7 +549,7 @@ class MeshService : Service(), Logging {
setChannel(it)
}
channels = asChannels.toTypedArray()
channels = fixupChannelList(asChannels).toTypedArray()
}
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
@ -946,28 +946,22 @@ class MeshService : Service(), Logging {
/// If we just changed our nodedb, we might want to do somethings
private fun onNodeDBChanged() {
maybeUpdateServiceStatusNotification()
serviceScope.handledLaunch(Dispatchers.Main) {
setupLocationRequest()
}
}
private var locationRequestInterval: Long = 0;
private fun setupLocationRequest() {
val desiredInterval: Long = if (myNodeInfo?.hasGPS == true) {
0L // no requests when device has GPS
} else if (numOnlineNodes < 2) {
5 * 60 * 1000L // send infrequently, device needs these requests to set its clock
var desiredInterval = 0L
if (myNodeInfo?.hasGPS == true)
desiredInterval =
radioConfig?.preferences?.positionBroadcastSecs?.times(1000L) ?: 5 * 60 * 1000L
stopLocationRequests()
if (desiredInterval != 0L) {
debug("desired GPS assistance interval $desiredInterval")
startLocationRequests(desiredInterval)
} else {
radioConfig?.preferences?.positionBroadcastSecs?.times(1000L) ?: 5 * 60 * 1000L
}
debug("desired location request $desiredInterval, current $locationRequestInterval")
if (desiredInterval != locationRequestInterval) {
if (locationRequestInterval > 0) stopLocationRequests()
if (desiredInterval > 0) startLocationRequests(desiredInterval)
locationRequestInterval = desiredInterval
debug("No GPS assistance desired, but sending UTC time to mesh")
sendPositionScoped()
}
}
@ -1315,13 +1309,44 @@ class MeshService : Service(), Logging {
radioConfig = null
// prefill the channel array with null channels
channels = Array(myInfo.maxChannels) {
val b = ChannelProtos.Channel.newBuilder()
b.index = it
b.build()
}
channels = fixupChannelList(listOf<ChannelProtos.Channel>()).toTypedArray()
}
/// scan the channel list and make sure it has one PRIMARY channel and is maxChannels long
private fun fixupChannelList(lIn: List<ChannelProtos.Channel>): List<ChannelProtos.Channel> {
val mi = myNodeInfo
var l = lIn
if (mi != null)
while (l.size < mi.maxChannels) {
val b = ChannelProtos.Channel.newBuilder()
b.index = l.size
l += b.build()
}
return l
}
private fun setRegionOnDevice() {
val curConfigRegion =
radioConfig?.preferences?.region ?: RadioConfigProtos.RegionCode.Unset
if (curConfigRegion.number != curRegionValue && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE)
if (deviceVersion >= minFirmwareVersion) {
info("Telling device to upgrade region")
// Tell the device to set the new region field (old devices will simply ignore this)
radioConfig?.let { currentConfig ->
val newConfig = currentConfig.toBuilder()
val newPrefs = currentConfig.preferences.toBuilder()
newPrefs.regionValue = curRegionValue
newConfig.preferences = newPrefs.build()
sendRadioConfig(newConfig.build())
}
} else
warn("Device is too old to understand region changes")
}
/**
* If we are updating nodes we might need to use old (fixed by firmware build)
@ -1356,24 +1381,7 @@ class MeshService : Service(), Logging {
}
// If nothing was set in our (new style radio preferences, but we now have a valid setting - slam it in)
if (curConfigRegion == RadioConfigProtos.RegionCode.Unset && curRegionValue != RadioConfigProtos.RegionCode.Unset_VALUE) {
if (deviceVersion >= minFirmwareVersion) {
info("Telling device to upgrade region")
// Tell the device to set the new region field (old devices will simply ignore this)
radioConfig?.let { currentConfig ->
val newConfig = currentConfig.toBuilder()
val newPrefs = currentConfig.preferences.toBuilder()
newPrefs.regionValue = curRegionValue
newConfig.preferences = newPrefs.build()
sendRadioConfig(newConfig.build())
}
}
else
warn("Device is too old to understand region changes")
}
setRegionOnDevice()
}
}
@ -1388,6 +1396,8 @@ class MeshService : Service(), Logging {
reportConnection()
updateRegion()
setupLocationRequest() // start sending location packets if needed
}
private fun handleConfigComplete(configCompleteId: Int) {
@ -1466,13 +1476,13 @@ class MeshService : Service(), Logging {
* Must be called from serviceScope. Use sendPositionScoped() for direct calls.
*/
private fun sendPosition(
lat: Double,
lon: Double,
alt: Int,
lat: Double = 0.0,
lon: Double = 0.0,
alt: Int = 0,
destNum: Int = DataPacket.NODENUM_BROADCAST,
wantResponse: Boolean = false
) {
debug("Sending our position to=$destNum lat=$lat, lon=$lon, alt=$alt")
debug("Sending our position/time to=$destNum lat=$lat, lon=$lon, alt=$alt")
val position = MeshProtos.Position.newBuilder().also {
it.longitudeI = Position.degI(lon)
@ -1499,15 +1509,15 @@ class MeshService : Service(), Logging {
}
private fun sendPositionScoped(
lat: Double,
lon: Double,
alt: Int,
lat: Double = 0.0,
lon: Double = 0.0,
alt: Int = 0,
destNum: Int = DataPacket.NODENUM_BROADCAST,
wantResponse: Boolean = false
) = serviceScope.handledLaunch {
try {
sendPosition(lat, lon, alt, destNum, wantResponse)
} catch (ex: RadioNotConnectedException) {
} catch (ex: BLEException) {
warn("Ignoring disconnected radio during gps location update")
}
}
@ -1627,8 +1637,10 @@ class MeshService : Service(), Logging {
} else {
debug("Creating firmware update coroutine")
updateJob = serviceScope.handledLaunch {
debug("Starting firmware update coroutine")
SoftwareUpdateService.doUpdate(this@MeshService, safe, filename)
exceptionReporter {
debug("Starting firmware update coroutine")
SoftwareUpdateService.doUpdate(this@MeshService, safe, filename)
}
}
}
}
@ -1660,7 +1672,7 @@ class MeshService : Service(), Logging {
offlineSentPackets.add(p)
}
val binder = object : IMeshService.Stub() {
private val binder = object : IMeshService.Stub() {
override fun setDeviceAddress(deviceAddr: String?) = toRemoteExceptions {
debug("Passing through device change to radio service: ${deviceAddr.anonymize}")
@ -1684,6 +1696,12 @@ class MeshService : Service(), Logging {
}
override fun getUpdateStatus(): Int = SoftwareUpdateService.progress
override fun getRegion(): Int = curRegionValue
override fun setRegion(regionCode: Int) = toRemoteExceptions {
curRegionValue = regionCode
setRegionOnDevice()
}
override fun startFirmwareUpdate() = toRemoteExceptions {
doFirmwareUpdate()

Wyświetl plik

@ -325,7 +325,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
if (newWork.timeoutMillis != 0L) {
activeTimeout = serviceScope.launch {
debug("Starting failsafe timer ${newWork.timeoutMillis}")
// debug("Starting failsafe timer ${newWork.timeoutMillis}")
delay(newWork.timeoutMillis)
errormsg("Failsafe BLE timer expired!")
completeWork(
@ -415,7 +415,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
if (work == null)
warn("wor completed, but we already killed it via failsafetimer? status=$status, res=$res")
else {
debug("work ${work.tag} is completed, resuming status=$status, res=$res")
// debug("work ${work.tag} is completed, resuming status=$status, res=$res")
if (status != 0)
work.completion.resumeWithException(
BLEStatusException(
@ -773,7 +773,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
closeGatt()
failAllWork(BLEException("Connection closing"))
failAllWork(BLEConnectionClosing())
}
/**

Wyświetl plik

@ -374,13 +374,17 @@ class SoftwareUpdateService : JobIntentService(), Logging {
throw DeviceRejectedException()
// Send all the blocks
var oldProgress = -1 // used to limit # of log spam
while (firmwareNumSent < firmwareSize) {
// If we are doing the spiffs partition, we limit progress to a max of 50%, so that the user doesn't think we are done
// yet
val maxProgress = if(flashRegion != FLASH_REGION_APPLOAD)
50 else 100
sendProgress(context, firmwareNumSent * maxProgress / firmwareSize, isAppload)
debug("sending block ${progress}%")
if(progress != oldProgress) {
debug("sending block ${progress}%")
oldProgress = progress;
}
var blockSize = 512 - 3 // Max size MTU excluding framing
if (blockSize > firmwareStream.available())

Wyświetl plik

@ -18,7 +18,6 @@ import com.geeksville.android.Logging
import com.geeksville.android.hideKeyboard
import com.geeksville.mesh.AppOnlyProtos
import com.geeksville.mesh.ChannelProtos
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.databinding.ChannelFragmentBinding
import com.geeksville.mesh.model.Channel
@ -50,6 +49,7 @@ fun ImageView.setOpaque() {
class ChannelFragment : ScreenFragment("Channel"), Logging {
private var _binding: ChannelFragmentBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding get() = _binding!!
@ -81,6 +81,12 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
val channels = model.channels.value
val channel = channels?.primaryChannel
val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED
// Only let buttons work if we are connected to the radio
binding.shareButton.isEnabled = connected
binding.resetButton.isEnabled = connected
binding.editableCheckbox.isChecked = false // start locked
if (channel != null) {
binding.qrView.visibility = View.VISIBLE
@ -89,7 +95,6 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
// For now, we only let the user edit/save channels while the radio is awake - because the service
// doesn't cache radioconfig writes.
val connected = model.isConnected.value == MeshService.ConnectionState.CONNECTED
binding.editableCheckbox.isEnabled = connected
binding.qrView.setImageBitmap(channels.getChannelQR())
@ -143,6 +148,28 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
}
}
/// Send new channel settings to the device
private fun installSettings(newChannel: ChannelProtos.ChannelSettings) {
val newSet =
ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build())
// Try to change the radio, if it fails, tell the user why and throw away their redits
try {
model.setChannels(newSet)
// Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc)
} catch (ex: RemoteException) {
errormsg("ignoring channel problem", ex)
setGUIfromModel() // Throw away user edits
// Tell the user to try again
Snackbar.make(
binding.editableCheckbox,
R.string.radio_sleeping,
Snackbar.LENGTH_SHORT
).show()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -150,6 +177,21 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
requireActivity().hideKeyboard()
}
binding.resetButton.setOnClickListener { _ ->
// User just locked it, we should warn and then apply changes to radio
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.reset_to_defaults)
.setMessage(R.string.are_you_shure_change_default)
.setNeutralButton(R.string.cancel) { _, _ ->
setGUIfromModel() // throw away any edits
}
.setPositiveButton(getString(R.string.accept)) { _, _ ->
debug("Switching back to default channel")
installSettings(Channel.defaultChannel.settings)
}
.show()
}
// Note: Do not use setOnCheckedChanged here because we don't want to be called when we programmatically disable editing
binding.editableCheckbox.setOnClickListener { _ ->
val checked = binding.editableCheckbox.isChecked
@ -178,41 +220,26 @@ class ChannelFragment : ScreenFragment("Channel"), Logging {
ignoreCase = true
)
) {
// Install a new customized channel
debug("ASSIGNING NEW AES256 KEY")
val random = SecureRandom()
val bytes = ByteArray(32)
random.nextBytes(bytes)
newSettings.psk = ByteString.copyFrom(bytes)
val selectedChannelOptionString =
binding.filledExposedDropdown.editableText.toString()
val modemConfig = getModemConfig(selectedChannelOptionString)
if (modemConfig != ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED)
newSettings.modemConfig = modemConfig
} else {
debug("Switching back to default channel")
newSettings = Channel.defaultChannel.settings.toBuilder()
}
val selectedChannelOptionString =
binding.filledExposedDropdown.editableText.toString()
val modemConfig = getModemConfig(selectedChannelOptionString)
if (modemConfig != ChannelProtos.ChannelSettings.ModemConfig.UNRECOGNIZED)
newSettings.modemConfig = modemConfig
val newChannel = newSettings.build()
val newSet = ChannelSet(AppOnlyProtos.ChannelSet.newBuilder().addSettings(newChannel).build())
// Try to change the radio, if it fails, tell the user why and throw away their redits
try {
model.setChannels(newSet)
// Since we are writing to radioconfig, that will trigger the rest of the GUI update (QR code etc)
} catch (ex: RemoteException) {
errormsg("ignoring channel problem", ex)
setGUIfromModel() // Throw away user edits
// Tell the user to try again
Snackbar.make(
binding.editableCheckbox,
R.string.radio_sleeping,
Snackbar.LENGTH_SHORT
).show()
}
installSettings(newSettings.build())
}
}
.show()

Wyświetl plik

@ -524,7 +524,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
}
/// Set the correct update button configuration based on current progress
private fun refreshUpdateButton() {
private fun refreshUpdateButton(enable: Boolean) {
debug("Reiniting the udpate button")
val info = model.myNodeInfo.value
val service = model.meshService
@ -535,7 +535,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
val progress = service.updateStatus
binding.updateFirmwareButton.isEnabled =
binding.updateFirmwareButton.isEnabled = enable &&
(progress < 0) // if currently doing an upgrade disable button
if (progress >= 0) {
@ -572,7 +572,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
val connected = model.isConnected.value
val isConnected = connected == MeshService.ConnectionState.CONNECTED
binding.nodeSettings.visibility = if(isConnected) View.VISIBLE else View.GONE
binding.nodeSettings.visibility = if (isConnected) View.VISIBLE else View.GONE
if (connected == MeshService.ConnectionState.DISCONNECTED)
model.ownerName.value = ""
@ -582,25 +582,19 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
val spinner = binding.regionSpinner
val unsetIndex = regions.indexOf(RadioConfigProtos.RegionCode.Unset.name)
spinner.onItemSelectedListener = null
if(region != null) {
debug("current region is $region")
var regionIndex = regions.indexOf(region.name)
if(regionIndex == -1) // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset
regionIndex = unsetIndex
// We don't want to be notified of our own changes, so turn off listener while making them
spinner.setSelection(regionIndex, false)
spinner.onItemSelectedListener = regionSpinnerListener
spinner.isEnabled = true
}
else {
warn("region is unset!")
spinner.setSelection(unsetIndex, false)
spinner.isEnabled = false // leave disabled, because we can't get our region
}
debug("current region is $region")
var regionIndex = regions.indexOf(region.name)
if (regionIndex == -1) // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset
regionIndex = unsetIndex
// We don't want to be notified of our own changes, so turn off listener while making them
spinner.setSelection(regionIndex, false)
spinner.onItemSelectedListener = regionSpinnerListener
spinner.isEnabled = true
// If actively connected possibly let the user update firmware
refreshUpdateButton()
refreshUpdateButton(region != RadioConfigProtos.RegionCode.Unset)
// Update the status string (highest priority messages first)
val info = model.myNodeInfo.value
@ -620,7 +614,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
}
}
private val regionSpinnerListener = object : AdapterView.OnItemSelectedListener{
private val regionSpinnerListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View,
@ -652,7 +646,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
// init our region spinner
val spinner = binding.regionSpinner
val regionAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, regions)
val regionAdapter =
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, regions)
regionAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = regionAdapter
@ -965,7 +960,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private val updateProgressReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
refreshUpdateButton()
refreshUpdateButton(true)
}
}

Wyświetl plik

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M14.99,4.59V5c0,1.1 -0.9,2 -2,2h-2v2c0,0.55 -0.45,1 -1,1h-2v2h6c0.55,0 1,0.45 1,1v3h1c0.89,0 1.64,0.59 1.9,1.4C19.19,15.98 20,14.08 20,12c0,-3.35 -2.08,-6.23 -5.01,-7.41zM8.99,16v-1l-4.78,-4.78C4.08,10.79 4,11.39 4,12c0,4.07 3.06,7.43 6.99,7.93V18c-1.1,0 -2,-0.9 -2,-2z"
android:strokeAlpha="0.3"
android:fillAlpha="0.3"/>
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10.99,19.93C7.06,19.43 4,16.07 4,12c0,-0.61 0.08,-1.21 0.21,-1.78L8.99,15v1c0,1.1 0.9,2 2,2v1.93zM17.89,17.4c-0.26,-0.81 -1,-1.4 -1.9,-1.4h-1v-3c0,-0.55 -0.45,-1 -1,-1h-6v-2h2c0.55,0 1,-0.45 1,-1L10.99,7h2c1.1,0 2,-0.9 2,-2v-0.41C17.92,5.77 20,8.65 20,12c0,2.08 -0.81,3.98 -2.11,5.4z"/>
</vector>

Wyświetl plik

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -91,30 +92,46 @@
<AutoCompleteTextView
android:id="@+id/filled_exposed_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
android:hint="@string/set_channel_options" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/resetButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/reset"
app:icon="@drawable/ic_twotone_public_24"
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
app:layout_constraintStart_toStartOf="parent" />
<CheckBox
android:id="@+id/editableCheckbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="96dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="8dp"
android:button="@drawable/sl_lock_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/shareButton"
app:layout_constraintStart_toStartOf="parent"></CheckBox>
app:layout_constraintBottom_toBottomOf="@id/bottomButtonsGuideline"
app:layout_constraintStart_toEndOf="@+id/resetButton"></CheckBox>
<ImageButton
android:id="@+id/shareButton"
style="@android:style/Widget.Material.ImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="96dp"
android:layout_marginEnd="32dp"
android:contentDescription="@string/share"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/editableCheckbox"
app:srcCompat="@drawable/ic_twotone_share_24" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/bottomButtonsGuideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:orientation="horizontal"
app:layout_constraintGuide_end="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

Wyświetl plik

@ -97,4 +97,8 @@
<string name="cant_change_no_radio">Couldn\'t change channel, because radio is not yet connected. Please try again.</string>
<string name="sample_coords">55.332244 34.442211</string>
<string name="save_messages">Save messages as csv...</string>
<string name="set_channel_options">Set channel options</string>
<string name="reset">Reset</string>
<string name="are_you_shure_change_default">Are you sure you want to change to the default channel?</string>
<string name="reset_to_defaults">Reset to defaults</string>
</resources>