chore: add detekt formatting rule set

https://detekt.dev/docs/next/rules/formatting/
This commit is contained in:
andrekir 2024-10-13 23:02:05 -03:00 committed by Andre K
parent 056d4a5829
commit fe56d257f5
58 changed files with 725 additions and 432 deletions

View file

@ -62,10 +62,11 @@ data class DataPacket(
* If this is a text message, return the string, otherwise null
*/
val text: String?
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE)
get() = if (dataType == Portnums.PortNum.TEXT_MESSAGE_APP_VALUE) {
bytes?.decodeToString()
else
} else {
null
}
constructor(to: String?, channel: Int, waypoint: MeshProtos.Waypoint) : this(
to = to,
@ -75,10 +76,11 @@ data class DataPacket(
)
val waypoint: MeshProtos.Waypoint?
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE)
get() = if (dataType == Portnums.PortNum.WAYPOINT_APP_VALUE) {
MeshProtos.Waypoint.parseFrom(bytes)
else
} else {
null
}
// Autogenerated comparision, because we have a byte array
@ -142,7 +144,7 @@ data class DataPacket(
return 0
}
/// Update our object from our parcel (used for inout parameters
// Update our object from our parcel (used for inout parameters
fun readFromParcel(parcel: Parcel) {
to = parcel.readString()
parcel.createByteArray()
@ -164,7 +166,7 @@ data class DataPacket(
/** The Node ID for the local node - used for from when sender doesn't know our local node ID */
const val ID_LOCAL = "^local"
/// special broadcast address
// special broadcast address
const val NODENUM_BROADCAST = (0xffffffff).toInt()
// Public-key cryptography (PKC) channel index
@ -181,4 +183,4 @@ data class DataPacket(
return arrayOfNulls(size)
}
}
}
}

View file

@ -212,7 +212,7 @@ class MainActivity : AppCompatActivity(), Logging {
tab.icon = ContextCompat.getDrawable(this, tabInfos[position].icon)
}.attach()
binding.tabLayout.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener {
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val mainTab = tab?.position ?: 0
model.setCurrentTab(mainTab)
@ -254,7 +254,7 @@ class MainActivity : AppCompatActivity(), Logging {
private var requestedChannelUrl: Uri? = null
/// Handle any itents that were passed into us
// Handle any intents that were passed into us
private fun handleIntent(intent: Intent) {
val appLinkAction = intent.action
val appLinkData: Uri? = intent.data
@ -327,7 +327,7 @@ class MainActivity : AppCompatActivity(), Logging {
showSettingsPage() // Default to the settings page in this case
}
/// Called when we gain/lose a connection to our mesh radio
// Called when we gain/lose a connection to our mesh radio
private fun onMeshConnectionChanged(newConnection: MeshService.ConnectionState) {
if (newConnection == MeshService.ConnectionState.CONNECTED) {
serviceRepository.meshService?.let { service ->
@ -336,17 +336,17 @@ class MainActivity : AppCompatActivity(), Logging {
if (info != null) {
val isOld = info.minAppVersion > BuildConfig.VERSION_CODE
if (isOld)
if (isOld) {
showAlert(R.string.app_too_old, R.string.must_update)
else {
} else {
// If we are already doing an update don't put up a dialog or try to get device info
val isUpdating = service.updateStatus >= 0
if (!isUpdating) {
val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0")
if (curVer < MeshService.minDeviceVersion)
if (curVer < MeshService.minDeviceVersion) {
showAlert(R.string.firmware_too_old, R.string.firmware_old)
else {
} else {
// If our app is too old/new, we probably don't understand the new DeviceConfig messages, so we don't read them until here
// we have a connection to our device now, do the channel change
@ -359,8 +359,9 @@ class MainActivity : AppCompatActivity(), Logging {
warn("Abandoning connect $ex, because we probably just lost device connection")
}
// if provideLocation enabled: Start providing location (from phone GPS) to mesh
if (model.provideLocation.value == true)
if (model.provideLocation.value == true) {
service.startProvideLocation()
}
}
if (!hasNotificationPermission()) {
@ -406,9 +407,9 @@ class MainActivity : AppCompatActivity(), Logging {
val channels = url.toChannelSet()
val shouldAdd = url.shouldAddChannels()
val primary = channels.primaryChannel
if (primary == null)
if (primary == null) {
showSnackbar(R.string.channel_invalid)
else {
} else {
val dialogMessage = if (!shouldAdd) {
getString(R.string.do_you_want_switch).format(primary.name)
} else {
@ -651,10 +652,11 @@ class MainActivity : AppCompatActivity(), Logging {
handler.postDelayed({ postPing() }, 30000)
}
item.isChecked = !item.isChecked // toggle ping test
if (item.isChecked)
if (item.isChecked) {
postPing()
else
} else {
handler.removeCallbacksAndMessages(null)
}
return true
}
R.id.radio_config -> {
@ -705,11 +707,11 @@ class MainActivity : AppCompatActivity(), Logging {
}
}
/// Theme functions
// Theme functions
private fun chooseThemeDialog() {
/// Prepare dialog and its items
// Prepare dialog and its items
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(getString(R.string.choose_theme))
@ -719,7 +721,7 @@ class MainActivity : AppCompatActivity(), Logging {
getString(R.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
/// Load preferences and its value
// Load preferences and its value
val prefs = UIViewModel.getPreferences(this)
val theme = prefs.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
debug("Theme from prefs: $theme")
@ -740,13 +742,13 @@ class MainActivity : AppCompatActivity(), Logging {
}
private fun chooseLangDialog() {
/// Prepare dialog and its items
// Prepare dialog and its items
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(getString(R.string.preferences_language))
val languageTags = LanguageUtils.getLanguageTags(this)
/// Load preferences and its value
// Load preferences and its value
val lang = LanguageUtils.getLocale()
debug("Lang from prefs: $lang")

View file

@ -150,8 +150,9 @@ fun Context.hasLocationPermission() = getLocationPermissions().isEmpty()
*/
fun Context.getNotificationPermissions(): Array<String> {
val perms = mutableListOf<String>()
if (android.os.Build.VERSION.SDK_INT >= 33)
if (android.os.Build.VERSION.SDK_INT >= 33) {
perms.add(Manifest.permission.POST_NOTIFICATIONS)
}
return getMissingPermissions(perms)
}

View file

@ -22,7 +22,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
var serviceP: T? = null
/// A getter that returns the bound service or throws if not bound
// A getter that returns the bound service or throws if not bound
val service: T
get() {
waitConnect() // Wait for at least the initial connection to happen
@ -40,11 +40,13 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
fun waitConnect() {
// Wait until this service is connected
lock.withLock {
if (context == null)
if (context == null) {
throw Exception("Haven't called connect")
}
if (serviceP == null)
if (serviceP == null) {
condition.await()
}
}
}
@ -71,8 +73,7 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
isClosed = true
try {
context?.unbindService(connection)
}
catch(ex: IllegalArgumentException) {
} catch (ex: IllegalArgumentException) {
// Autobugs show this can generate an illegal arg exception for "service not registered" during reinstall?
warn("Ignoring error in ServiceClient.close, probably harmless")
}
@ -80,11 +81,11 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
context = null
}
/// Called when we become connected
// Called when we become connected
open fun onConnected(service: T) {
}
/// called on loss of connection
// called on loss of connection
open fun onDisconnected() {
}
@ -111,4 +112,4 @@ open class ServiceClient<T : IInterface>(private val stubFactory: (IBinder) -> T
onDisconnected()
}
}
}
}

View file

@ -2,7 +2,6 @@ package com.geeksville.mesh.concurrent
import com.geeksville.mesh.android.Logging
/**
* A deferred execution object (with various possible implementations)
*/
@ -52,15 +51,17 @@ class SyncContinuation<T> : Continuation<T> {
while (result == null) {
mbox.wait(timeoutMsecs)
if (timeoutMsecs > 0 && ((System.currentTimeMillis() - startT) >= timeoutMsecs))
if (timeoutMsecs > 0 && ((System.currentTimeMillis() - startT) >= timeoutMsecs)) {
throw Exception("SyncContinuation timeout")
}
}
val r = result
if (r != null)
if (r != null) {
return r.getOrThrow()
else
} else {
throw Exception("This shouldn't happen")
}
}
}
}

View file

@ -29,15 +29,15 @@ import com.geeksville.mesh.database.entity.QuickChatAction
QuickChatAction::class
],
autoMigrations = [
AutoMigration (from = 3, to = 4),
AutoMigration (from = 4, to = 5),
AutoMigration (from = 5, to = 6),
AutoMigration (from = 6, to = 7),
AutoMigration (from = 7, to = 8),
AutoMigration (from = 8, to = 9),
AutoMigration (from = 9, to = 10),
AutoMigration (from = 10, to = 11),
AutoMigration (from = 11, to = 12),
AutoMigration(from = 3, to = 4),
AutoMigration(from = 4, to = 5),
AutoMigration(from = 5, to = 6),
AutoMigration(from = 6, to = 7),
AutoMigration(from = 7, to = 8),
AutoMigration(from = 8, to = 9),
AutoMigration(from = 9, to = 10),
AutoMigration(from = 10, to = 11),
AutoMigration(from = 11, to = 12),
AutoMigration(from = 12, to = 13, spec = AutoMigration12to13::class),
],
version = 13,

View file

@ -29,5 +29,4 @@ interface MeshLogDao {
@Query("DELETE FROM log")
fun deleteAll()
}
}

View file

@ -164,7 +164,7 @@ interface PacketDao {
fun getContactSettings(): Flow<Map<@MapColumn(columnName = "contact_key") String, ContactSettings>>
@Query("SELECT * FROM contact_settings WHERE contact_key = :contact")
suspend fun getContactSettings(contact:String): ContactSettings?
suspend fun getContactSettings(contact: String): ContactSettings?
@Upsert
fun upsertContactSettings(contacts: List<ContactSettings>)

View file

@ -107,9 +107,9 @@ data class NodeEntity(
val validPosition: MeshProtos.Position? get() = position.takeIf { hasValidPosition() }
// @return distance in meters to some other node (or null if unknown)
fun distance(o: NodeEntity): Int? {
return if (validPosition == null || o.validPosition == null) null
else latLongToMeter(latitude, longitude, o.latitude, o.longitude).toInt()
fun distance(o: NodeEntity): Int? = when {
validPosition == null || o.validPosition == null -> null
else -> latLongToMeter(latitude, longitude, o.latitude, o.longitude).toInt()
}
// @return a nice human readable string for the distance, or null for unknown
@ -119,9 +119,9 @@ data class NodeEntity(
}
// @return bearing to the other position in degrees
fun bearing(o: NodeEntity?): Int? {
return if (validPosition == null || o?.validPosition == null) null
else bearing(latitude, longitude, o.latitude, o.longitude).toInt()
fun bearing(o: NodeEntity?): Int? = when {
validPosition == null || o?.validPosition == null -> null
else -> bearing(latitude, longitude, o.latitude, o.longitude).toInt()
}
fun gpsString(gpsFormat: Int): String = when (gpsFormat) {
@ -140,7 +140,9 @@ data class NodeEntity(
} else {
"%.1f°C".format(temperature)
}
} else null
} else {
null
}
val humidity = if (relativeHumidity != 0f) "%.0f%%".format(relativeHumidity) else null
val pressure = if (barometricPressure != 0f) "%.1fhPa".format(barometricPressure) else null
val gas = if (gasResistance != 0f) "%.0fMΩ".format(gasResistance) else null
@ -188,7 +190,7 @@ data class NodeEntity(
}
companion object {
/// Convert to a double representation of degrees
// Convert to a double representation of degrees
fun degD(i: Int) = i * 1e-7
fun degI(d: Double) = (d * 1e7).toInt()

View file

@ -7,12 +7,13 @@ import androidx.room.PrimaryKey
@Entity(tableName = "quick_chat")
data class QuickChatAction(
@PrimaryKey(autoGenerate = true) val uuid: Long,
@ColumnInfo(name="name") val name: String,
@ColumnInfo(name="message") val message: String,
@ColumnInfo(name="mode") val mode: Mode,
@ColumnInfo(name="position") val position: Int) {
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "message") val message: String,
@ColumnInfo(name = "mode") val mode: Mode,
@ColumnInfo(name = "position") val position: Int
) {
enum class Mode {
Append,
Instant,
}
}
}

View file

@ -47,7 +47,7 @@ data class Channel(
}
}
/// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
// Return the name of our channel as a human readable string. If empty string, assume "Default" per mesh.proto spec
val name: String
get() = settings.name.ifEmpty {
// We have a new style 'empty' channel name. Use the same logic from the device to convert that to a human readable name
@ -66,15 +66,15 @@ data class Channel(
}
val psk: ByteString
get() = if (settings.psk.size() != 1)
get() = if (settings.psk.size() != 1) {
settings.psk // A standard PSK
else {
} else {
// One of our special 1 byte PSKs, see mesh.proto for docs.
val pskIndex = settings.psk.byteAt(0).toInt()
if (pskIndex == 0)
if (pskIndex == 0) {
cleartextPSK
else {
} else {
// Treat an index of 1 as the old channelDefaultKey and work up from there
val bytes = channelDefaultKey.clone()
bytes[bytes.size - 1] = (0xff and (bytes[bytes.size - 1] + pskIndex - 1)).toByte()

View file

@ -40,8 +40,9 @@ private fun LoRaConfig.bandwidth() = if (usePreset) {
val LoRaConfig.numChannels: Int get() {
for (option in RegionInfo.entries) {
if (option.regionCode == region)
if (option.regionCode == region) {
return ((option.freqEnd - option.freqStart) / bandwidth()).toInt()
}
}
return 0
}
@ -55,8 +56,9 @@ internal fun LoRaConfig.channelNum(primaryName: String): Int = when {
internal fun LoRaConfig.radioFreq(channelNum: Int): Float {
if (overrideFrequency != 0f) return overrideFrequency + frequencyOffset
for (option in RegionInfo.entries) {
if (option.regionCode == region)
if (option.regionCode == region) {
return (option.freqStart + bandwidth() / 2) + (channelNum - 1) * bandwidth()
}
}
return 0f
}
@ -103,5 +105,4 @@ enum class ChannelOption(
LONG_MODERATE(ModemPreset.LONG_MODERATE, R.string.modem_config_mod_long, .125f),
LONG_SLOW(ModemPreset.LONG_SLOW, R.string.modem_config_slow_long, .125f),
VERY_LONG_SLOW(ModemPreset.VERY_LONG_SLOW, R.string.modem_config_very_long, .0625f),
;
}

View file

@ -469,8 +469,11 @@ class RadioConfigViewModel @Inject constructor(
setResponseStateError(app.getString(parsed.errorReason.stringRes))
} else if (packet.from == destNum && route.isEmpty()) {
requestIds.update { it.apply { remove(data.requestId) } }
if (requestIds.value.isEmpty()) setResponseStateSuccess()
else incrementCompleted()
if (requestIds.value.isEmpty()) {
setResponseStateSuccess()
} else {
incrementCompleted()
}
}
}
if (data?.portnumValue == Portnums.PortNum.ADMIN_APP_VALUE) {

View file

@ -58,10 +58,10 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.math.roundToInt
// / Given a human name, strip out the first letter of the first three words and return that as the initials for
// / that user. If the original name is only one word, strip vowels from the original name and if the result is
// / 3 or more characters, use the first three characters. If not, just take the first 3 characters of the
// / original name.
// Given a human name, strip out the first letter of the first three words and return that as the initials for
// that user. If the original name is only one word, strip vowels from the original name and if the result is
// 3 or more characters, use the first three characters. If not, just take the first 3 characters of the
// original name.
fun getInitials(nameIn: String): String {
val nchars = 4
val minchars = 2
@ -70,10 +70,11 @@ fun getInitials(nameIn: String): String {
val initials = when (words.size) {
in 0 until minchars -> {
val nm = if (name.isNotEmpty())
val nm = if (name.isNotEmpty()) {
name.first() + name.drop(1).filterNot { c -> c.lowercase() in "aeiou" }
else
} else {
""
}
if (nm.length >= nchars) nm else name
}
else -> words.map { it.first() }.joinToString("")
@ -414,7 +415,7 @@ class UIViewModel @Inject constructor(
fun requestUserInfo(destNum: Int) {
info("Requesting UserInfo for '$destNum'")
try {
meshService?.requestUserInfo( destNum )
meshService?.requestUserInfo(destNum)
} catch (ex: RemoteException) {
errormsg("Request NodeInfo error: ${ex.message}")
}
@ -545,7 +546,7 @@ class UIViewModel @Inject constructor(
fun setChannels(channelSet: AppOnlyProtos.ChannelSet, overwrite: Boolean = true) = viewModelScope.launch {
val newRadioSettings: List<ChannelSettings> = if (overwrite) {
channelSet.settingsList
} else {
} else {
// To guarantee consistent ordering, using a LinkedHashSet which iterates through it's
// entries according to the order an item was *first* inserted.
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-linked-hash-set/

View file

@ -43,7 +43,6 @@ class MarkerWithLabel(mapView: MapView?, label: String, emoji: String? = null) :
}
}
private var onLongClickListener: (() -> Boolean)? = null
fun setOnLongClickListener(listener: () -> Boolean) {
@ -93,7 +92,7 @@ class MarkerWithLabel(mapView: MapView?, label: String, emoji: String? = null) :
val bgRect = getTextBackgroundSize(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET))
bgRect.inset(-8F, -2F)
if(mLabel.isNotEmpty()) {
if (mLabel.isNotEmpty()) {
c.drawRoundRect(bgRect, LABEL_CORNER_RADIUS, LABEL_CORNER_RADIUS, bgPaint)
c.drawText(mLabel, (p.x - 0F), (p.y - LABEL_Y_OFFSET), textPaint)
}

View file

@ -11,7 +11,7 @@ import javax.inject.Inject
class BluetoothInterfaceSpec @Inject constructor(
private val factory: BluetoothInterfaceFactory,
private val bluetoothRepository: BluetoothRepository,
): InterfaceSpec<BluetoothInterface>, Logging {
) : InterfaceSpec<BluetoothInterface>, Logging {
override fun createInterface(rest: String): BluetoothInterface {
return factory.create(rest)
}
@ -23,7 +23,8 @@ class BluetoothInterfaceSpec @Inject constructor(
return if (!allPaired.contains(rest)) {
warn("Ignoring stale bond to ${rest.anonymize}")
false
} else
} else {
true
}
}
}
}

View file

@ -12,7 +12,7 @@ import javax.inject.Provider
class InterfaceFactory @Inject constructor(
private val nopInterfaceFactory: NopInterfaceFactory,
private val specMap: Map<InterfaceId, @JvmSuppressWildcards Provider<InterfaceSpec<*>>>
) {
) {
internal val nopInterface by lazy {
nopInterfaceFactory.create("")
}
@ -38,4 +38,4 @@ class InterfaceFactory @Inject constructor(
val rest = address.substring(1)
return Pair(c, rest)
}
}
}

View file

@ -9,6 +9,6 @@ package com.geeksville.mesh.repository.radio
* This is primarily used in conjunction with Dagger assisted injection for each backend
* interface type.
*/
interface InterfaceFactorySpi<T: IRadioInterface> {
interface InterfaceFactorySpi<T : IRadioInterface> {
fun create(rest: String): T
}
}

View file

@ -7,7 +7,7 @@ import javax.inject.Inject
*/
class MockInterfaceSpec @Inject constructor(
private val factory: MockInterfaceFactory
): InterfaceSpec<MockInterface> {
) : InterfaceSpec<MockInterface> {
override fun createInterface(rest: String): MockInterface {
return factory.create(rest)
}

View file

@ -7,8 +7,8 @@ import javax.inject.Inject
*/
class NopInterfaceSpec @Inject constructor(
private val factory: NopInterfaceFactory
): InterfaceSpec<NopInterface> {
) : InterfaceSpec<NopInterface> {
override fun createInterface(rest: String): NopInterface {
return factory.create(rest)
}
}
}

View file

@ -30,7 +30,6 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
/**
* Handles the bluetooth link with a mesh radio device. Does not cache any device state,
* just does bluetooth comms etc...
@ -79,7 +78,7 @@ class RadioInterfaceService @Inject constructor(
*/
private var isStarted = false
/// true if our interface is currently connected to a device
// true if our interface is currently connected to a device
private var isConnected = false
private fun initStateListeners() {
@ -156,7 +155,7 @@ class RadioInterfaceService @Inject constructor(
}
}
/// Send a packet/command out the radio link, this routine can block if it needs to
// Send a packet/command out the radio link, this routine can block if it needs to
private fun handleSendToRadio(p: ByteArray) {
radioIf.handleSendToRadio(p)
}
@ -191,20 +190,22 @@ class RadioInterfaceService @Inject constructor(
/** Start our configured interface (if it isn't already running) */
private fun startInterface() {
if (radioIf !is NopInterface)
if (radioIf !is NopInterface) {
warn("Can't start interface - $radioIf is already running")
else {
} else {
val address = getBondedDeviceAddress()
if (address == null)
if (address == null) {
warn("No bonded mesh radio, can't start interface")
else {
} else {
info("Starting radio ${address.anonymize}")
isStarted = true
if (logSends)
if (logSends) {
sentPacketsLog = BinaryLogFile(context, "sent_log.pb")
if (logReceives)
}
if (logReceives) {
receivedPacketsLog = BinaryLogFile(context, "receive_log.pb")
}
radioIf = interfaceFactory.createInterface(address)
}
@ -222,17 +223,19 @@ class RadioInterfaceService @Inject constructor(
serviceScope.cancel("stopping interface")
serviceScope = CoroutineScope(Dispatchers.IO + Job())
if (logSends)
if (logSends) {
sentPacketsLog.close()
if (logReceives)
}
if (logReceives) {
receivedPacketsLog.close()
}
// Don't broadcast disconnects if we were just using the nop device
if (r !is NopInterface)
if (r !is NopInterface) {
onDisconnect(isPermanent = true) // Tell any clients we are now offline
}
}
/**
* Change to a new device
*
@ -258,10 +261,11 @@ class RadioInterfaceService @Inject constructor(
debug("Setting bonded device to ${address.anonymize}")
prefs.edit {
if (address == null)
if (address == null) {
this.remove(DEVADDR_KEY)
else
} else {
putString(DEVADDR_KEY, address)
}
}
// Force the service to reconnect

View file

@ -12,7 +12,7 @@ class SerialInterfaceSpec @Inject constructor(
private val factory: SerialInterfaceFactory,
private val usbManager: dagger.Lazy<UsbManager>,
private val usbRepository: UsbRepository,
): InterfaceSpec<SerialInterface> {
) : InterfaceSpec<SerialInterface> {
override fun createInterface(rest: String): SerialInterface {
return factory.create(rest)
}
@ -37,4 +37,4 @@ class SerialInterfaceSpec @Inject constructor(
deviceMap.map { (_, driver) -> driver }.firstOrNull()
}
}
}
}

View file

@ -2,7 +2,6 @@ package com.geeksville.mesh.repository.radio
import com.geeksville.mesh.android.Logging
/**
* An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP probably)
*/
@ -49,7 +48,7 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
abstract fun sendBytes(p: ByteArray)
/// If subclasses need to flash at the end of a packet they can implement
// If subclasses need to flash at the end of a packet they can implement
open fun flushBytes() {}
override fun handleSendToRadio(p: ByteArray) {
@ -66,7 +65,6 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
flushBytes()
}
/** Print device serial debug output somewhere */
private fun debugOut(b: Byte) {
when (val c = b.toChar()) {
@ -92,7 +90,7 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
nextPtr = 0
}
/// Deliver our current packet and restart our reader
// Deliver our current packet and restart our reader
fun deliverPacket() {
val buf = rxPacket.copyOf(packetLen)
service.handleFromRadio(buf)
@ -107,8 +105,9 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
nextPtr = 0 // Restart from scratch
}
1 -> // Looking for START2
if (c != START2)
if (c != START2) {
lostSync() // Restart from scratch
}
2 -> // Looking for MSB of our 16 bit length
msb = c.toInt() and 0xff
3 -> { // Looking for LSB of our 16 bit length
@ -116,10 +115,11 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
// We've read our header, do one big read for the packet itself
packetLen = (msb shl 8) or lsb
if (packetLen > MAX_TO_FROM_RADIO_SIZE)
lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for START1 again
else if (packetLen == 0)
if (packetLen > MAX_TO_FROM_RADIO_SIZE) {
lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for START1 again
} else if (packetLen == 0) {
deliverPacket() // zero length packets are valid and should be delivered immediately (because there won't be a next byte of payload)
}
}
else -> {
// We are looking at the packet bytes now
@ -133,4 +133,4 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
}
ptr = nextPtr
}
}
}

View file

@ -7,8 +7,8 @@ import javax.inject.Inject
*/
class TCPInterfaceSpec @Inject constructor(
private val factory: TCPInterfaceFactory
): InterfaceSpec<TCPInterface> {
) : InterfaceSpec<TCPInterface> {
override fun createInterface(rest: String): TCPInterface {
return factory.create(rest)
}
}
}

View file

@ -16,7 +16,7 @@ internal class SerialConnectionImpl(
private val device: UsbSerialDriver,
private val listener: SerialConnectionListener
) : SerialConnection, Logging {
private val port = device.ports[0] // Most devices have just one port (port 0)
private val port = device.ports[0] // Most devices have just one port (port 0)
private val closedLatch = CountDownLatch(1)
private val closed = AtomicBoolean(false)
private val ioRef = AtomicReference<SerialInputOutputManager>()
@ -80,7 +80,7 @@ internal class SerialConnectionImpl(
listener.onDisconnected(e)
}
}).apply {
readTimeout = 200 // To save battery we only timeout ever so often
readTimeout = 200 // To save battery we only timeout ever so often
ioRef.set(this)
}
@ -92,4 +92,4 @@ internal class SerialConnectionImpl(
listener.onConnected()
}
}
}

View file

@ -38,7 +38,7 @@ class UsbRepository @Inject constructor(
@Suppress("unused") // Retained as public API
val serialDevicesWithDrivers = _serialDevices
.mapLatest { serialDevices ->
.mapLatest { serialDevices ->
val serialProber = usbSerialProberLazy.get()
buildMap {
serialDevices.forEach { (k, v) ->
@ -72,7 +72,7 @@ class UsbRepository @Inject constructor(
* Creates a USB serial connection to the specified USB device. State changes and data arrival
* result in async callbacks on the supplied listener.
*/
fun createSerialConnection(device: UsbSerialDriver, listener: SerialConnectionListener) : SerialConnection {
fun createSerialConnection(device: UsbSerialDriver, listener: SerialConnectionListener): SerialConnection {
return SerialConnectionImpl(usbManagerLazy, device, listener)
}
@ -88,4 +88,4 @@ class UsbRepository @Inject constructor(
private suspend fun refreshStateInternal() = withContext(dispatchers.default) {
_serialDevices.emit(usbManagerLazy.get()?.deviceList ?: emptyMap())
}
}
}

View file

@ -88,11 +88,11 @@ class MeshService : Service(), Logging {
companion object : Logging {
/// Intents broadcast by MeshService
// Intents broadcast by MeshService
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
// 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 {
val portType = Portnums.PortNum.forNumber(portNum)
val portStr = portType?.toString() ?: portNum.toString()
@ -141,7 +141,7 @@ class MeshService : Service(), Logging {
private var previousSummary: String? = null
private var previousStats: LocalStats? = null
/// A mapping of receiver class name to package name - used for explicit broadcasts
// A mapping of receiver class name to package name - used for explicit broadcasts
private val clientPackages = mutableMapOf<String, String>()
private val serviceNotifications = MeshServiceNotifications(this)
private val serviceBroadcasts = MeshServiceBroadcasts(this, clientPackages) {
@ -337,9 +337,9 @@ class MeshService : Service(), Logging {
serviceJob.cancel()
}
///
/// BEGINNING OF MODEL - FIXME, move elsewhere
///
//
// BEGINNING OF MODEL - FIXME, move elsewhere
//
private fun loadSettings() {
discardNodeDB() // Get rid of any old state
@ -368,7 +368,7 @@ class MeshService : Service(), Logging {
private var moduleConfig: LocalModuleConfig = LocalModuleConfig.getDefaultInstance()
private var channelSet: AppOnlyProtos.ChannelSet = AppOnlyProtos.ChannelSet.getDefaultInstance()
/// True after we've done our initial node db init
// True after we've done our initial node db init
@Volatile
private var haveNodeDB = false
@ -379,15 +379,15 @@ class MeshService : Service(), Logging {
// NOTE: some NodeInfos might be in only nodeDBbyNodeNum (because we don't yet know an ID).
private val nodeDBbyID get() = nodeDBbyNodeNum.mapKeys { it.value.user.id }
///
/// END OF MODEL
///
//
// END OF MODEL
//
private val deviceVersion get() = DeviceVersion(myNodeInfo?.firmwareVersion ?: "")
private val appVersion get() = BuildConfig.VERSION_CODE
private val minAppVersion get() = myNodeInfo?.minAppVersion ?: 0
/// Map a nodenum to a node, or throw an exception if not found
// Map a nodenum to a node, or throw an exception if not found
private fun toNodeInfo(n: Int) = nodeDBbyNodeNum[n] ?: throw NodeNumNotFoundException(n)
/** Map a nodeNum to the nodeId string
@ -395,8 +395,11 @@ class MeshService : Service(), Logging {
but some nodes might not have a user record at all (because not yet received), in that case, we return
a hex version of the ID just based on the number */
private fun toNodeID(n: Int): String =
if (n == DataPacket.NODENUM_BROADCAST) DataPacket.ID_BROADCAST
else nodeDBbyNodeNum[n]?.user?.id ?: DataPacket.nodeNumToDefaultId(n)
if (n == DataPacket.NODENUM_BROADCAST) {
DataPacket.ID_BROADCAST
} else {
nodeDBbyNodeNum[n]?.user?.id ?: DataPacket.nodeNumToDefaultId(n)
}
// given a nodeNum, return a db entry - creating if necessary
private fun getOrCreateNodeInfo(n: Int) = nodeDBbyNodeNum.getOrPut(n) {
@ -418,8 +421,8 @@ class MeshService : Service(), Logging {
private val hexIdRegex = """\!([0-9A-Fa-f]+)""".toRegex()
private val rangeTestRegex = Regex("seq (\\d{1,10})")
/// Map a userid to a node/ node num, or throw an exception if not found
/// We prefer to find nodes based on their assigned IDs, but if no ID has been assigned to a node, we can also find it based on node number
// Map a userid to a node/ node num, or throw an exception if not found
// We prefer to find nodes based on their assigned IDs, but if no ID has been assigned to a node, we can also find it based on node number
private fun toNodeInfo(id: String): NodeEntity {
// If this is a valid hexaddr will be !null
val hexStr = hexIdRegex.matchEntire(id)?.groups?.get(1)?.value
@ -454,7 +457,7 @@ class MeshService : Service(), Logging {
else -> toNodeInfo(id).num
}
/// A helper function that makes it easy to update node info objects
// A helper function that makes it easy to update node info objects
private inline fun updateNodeInfo(
nodeNum: Int,
withBroadcast: Boolean = true,
@ -463,25 +466,26 @@ class MeshService : Service(), Logging {
val info = getOrCreateNodeInfo(nodeNum)
updateFn(info)
if (info.user.id.isNotEmpty()) {
if (haveNodeDB) serviceScope.handledLaunch {
if (info.user.id.isNotEmpty() && haveNodeDB) {
serviceScope.handledLaunch {
radioConfigRepository.upsert(info)
}
}
if (withBroadcast)
if (withBroadcast) {
serviceBroadcasts.broadcastNodeChange(info.toNodeInfo())
}
}
/// My node num
// My node num
private val myNodeNum
get() = myNodeInfo?.myNodeNum
?: throw RadioNotConnectedException("We don't yet have our myNodeInfo")
/// My node ID string
// My node ID string
private val myNodeID get() = toNodeID(myNodeNum)
/// Admin channel index
// Admin channel index
private val MeshPacket.Builder.adminChannelIndex: Int
get() = when {
myNodeNum == to -> 0
@ -493,10 +497,11 @@ class MeshService : Service(), Logging {
.coerceAtLeast(0)
}
/// Generate a new mesh packet builder with our node as the sender, and the specified node num
// Generate a new mesh packet builder with our node as the sender, and the specified node num
private fun newMeshPacketTo(idNum: Int) = MeshPacket.newBuilder().apply {
if (myNodeInfo == null)
if (myNodeInfo == null) {
throw RadioNotConnectedException()
}
from = 0 // don't add myNodeNum
@ -540,7 +545,6 @@ class MeshService : Service(), Logging {
return build()
}
/**
* Helper to make it easy to build a subpacket in the proper protobufs
*/
@ -562,7 +566,7 @@ class MeshService : Service(), Logging {
}.build().toByteString()
}
/// Generate a DataPacket from a MeshPacket, or null if we didn't have enough data to do so
// Generate a DataPacket from a MeshPacket, or null if we didn't have enough data to do so
private fun toDataPacket(packet: MeshPacket): DataPacket? {
return if (!packet.hasDecoded()) {
// We never convert packets that are not DataPackets
@ -631,7 +635,7 @@ class MeshService : Service(), Logging {
}
}
/// Update our model and resend as needed for a MeshPacket we just received from the radio
// Update our model and resend as needed for a MeshPacket we just received from the radio
private fun handleReceivedData(packet: MeshPacket) {
myNodeInfo?.let { myInfo ->
val data = packet.decoded
@ -650,7 +654,7 @@ class MeshService : Service(), Logging {
// if (p.hasUser()) handleReceivedUser(fromNum, p.user)
/// We tell other apps about most message types, but some may have sensitve data, so that is not shared'
// We tell other apps about most message types, but some may have sensitive data, so that is not shared'
var shouldBroadcast = !fromUs
when (data.portnumValue) {
@ -747,8 +751,9 @@ class MeshService : Service(), Logging {
}
// We always tell other apps when new data packets arrive
if (shouldBroadcast)
if (shouldBroadcast) {
serviceBroadcasts.broadcastReceivedData(dataPacket)
}
GeeksvilleApplication.analytics.track(
"num_data_receive",
@ -784,9 +789,7 @@ class MeshService : Service(), Logging {
}
}
}
else ->
warn("No special processing needed for ${a.payloadVariantCase}")
else -> warn("No special processing needed for ${a.payloadVariantCase}")
}
} else {
debug("Admin: Received session_passkey from $fromNodeNum")
@ -794,7 +797,7 @@ class MeshService : Service(), Logging {
}
}
/// Update our DB of users based on someone sending out a User subpacket
// Update our DB of users based on someone sending out a User subpacket
private fun handleReceivedUser(fromNum: Int, p: MeshProtos.User, channel: Int = 0) {
updateNodeInfo(fromNum) {
val keyMatch = !it.hasPKC || it.user.publicKey == p.publicKey
@ -829,20 +832,14 @@ class MeshService : Service(), Logging {
}
}
private fun handleLocalStats(stats: TelemetryProtos.Telemetry) {
localStatsTelemetry = stats
maybeUpdateServiceStatusNotification()
}
/// Update our DB of users based on someone sending out a Telemetry subpacket
// Update our DB of users based on someone sending out a Telemetry subpacket
private fun handleReceivedTelemetry(
fromNum: Int,
t: TelemetryProtos.Telemetry,
) {
if (t.hasLocalStats()) {
handleLocalStats(t)
localStatsTelemetry = t
maybeUpdateServiceStatusNotification()
}
updateNodeInfo(fromNum) {
when {
@ -903,7 +900,9 @@ class MeshService : Service(), Logging {
private fun formatTraceroutePath(nodesList: List<Int>, snrList: List<Int>): String {
// nodesList should include both origin and destination nodes
// origin will not have an SNR value, but destination should
val snrStr = if (snrList.size == nodesList.size - 1) snrList else {
val snrStr = if (snrList.size == nodesList.size - 1) {
snrList
} else {
// use unknown SNR for entire route if snrList has invalid size
List(nodesList.size - 1) { -128 }
}.map { snr ->
@ -914,7 +913,7 @@ class MeshService : Service(), Logging {
return nodesList.map { nodeId ->
"${getUserName(nodeId)}"
}.flatMapIndexed { i, nodeStr ->
if (i == 0) listOf(nodeStr) else listOf(snrStr[i-1], nodeStr)
if (i == 0) listOf(nodeStr) else listOf(snrStr[i - 1], nodeStr)
}.joinToString("\n")
}
@ -945,15 +944,15 @@ class MeshService : Service(), Logging {
// If apps try to send packets when our radio is sleeping, we queue them here instead
private val offlineSentPackets = mutableListOf<DataPacket>()
/// Update our model and resend as needed for a MeshPacket we just received from the radio
// Update our model and resend as needed for a MeshPacket we just received from the radio
private fun handleReceivedMeshPacket(packet: MeshPacket) {
if (haveNodeDB) {
processReceivedMeshPacket(packet)
onNodeDBChanged()
} else {
warn("Ignoring early received packet: ${packet.toOneLineString()}")
//earlyReceivedPackets.add(packet)
//logAssert(earlyReceivedPackets.size < 128) // The max should normally be about 32, but if the device is messed up it might try to send forever
// earlyReceivedPackets.add(packet)
// logAssert(earlyReceivedPackets.size < 128) // The max should normally be about 32, but if the device is messed up it might try to send forever
}
}
@ -1072,13 +1071,13 @@ class MeshService : Service(), Logging {
}
}
/// Update our model and resend as needed for a MeshPacket we just received from the radio
// Update our model and resend as needed for a MeshPacket we just received from the radio
private fun processReceivedMeshPacket(packet: MeshPacket) {
val fromNum = packet.from
// FIXME, perhaps we could learn our node ID by looking at any to packets the radio
// decided to pass through to us (except for broadcast packets)
//val toNum = packet.to
// val toNum = packet.to
// debug("Recieved: $packet")
if (packet.hasDecoded()) {
@ -1160,8 +1159,7 @@ class MeshService : Service(), Logging {
private fun currentSecond() = (System.currentTimeMillis() / 1000).toInt()
/// If we just changed our nodedb, we might want to do somethings
// If we just changed our nodedb, we might want to do somethings
private fun onNodeDBChanged() {
maybeUpdateServiceStatusNotification()
}
@ -1189,14 +1187,14 @@ class MeshService : Service(), Logging {
private var sleepTimeout: Job? = null
/// msecs since 1970 we started this connection
// msecs since 1970 we started this connection
private var connectTimeMsec = 0L
/// Called when we gain/lose connection to our radio
// Called when we gain/lose connection to our radio
private fun onConnectionChanged(c: ConnectionState) {
debug("onConnectionChanged: $connectionState -> $c")
/// Perform all the steps needed once we start waiting for device sleep to complete
// Perform all the steps needed once we start waiting for device sleep to complete
fun startDeviceSleep() {
stopPacketQueue()
stopLocationRequests()
@ -1355,13 +1353,13 @@ class MeshService : Service(), Logging {
}
}
/// A provisional MyNodeInfo that we will install if all of our node config downloads go okay
// A provisional MyNodeInfo that we will install if all of our node config downloads go okay
private var newMyNodeInfo: MyNodeEntity? = null
/// provisional NodeInfos we will install if all goes well
// provisional NodeInfos we will install if all goes well
private val newNodes = mutableListOf<MeshProtos.NodeInfo>()
/// Used to make sure we never get foold by old BLE packets
// Used to make sure we never get foold by old BLE packets
private var configNonce = 1
private fun handleDeviceConfig(config: ConfigProtos.Config) {
@ -1400,8 +1398,11 @@ class MeshService : Service(), Logging {
Triple(res == 0, free == 0, meshPacketId)
}
if (success && isFull) return // Queue is full, wait for free != 0
if (requestId != 0) queueResponse.remove(requestId)?.complete(success)
else queueResponse.entries.lastOrNull { !it.value.isDone }?.value?.complete(success)
if (requestId != 0) {
queueResponse.remove(requestId)?.complete(success)
} else {
queueResponse.entries.lastOrNull { !it.value.isDone }?.value?.complete(success)
}
}
private fun handleChannel(ch: ChannelProtos.Channel) {
@ -1484,9 +1485,9 @@ class MeshService : Service(), Logging {
val mi = with(myInfo) {
MyNodeEntity(
myNodeNum = myNodeNum,
model = rawDeviceMetadata?.hwModel?.let { hwModel ->
if (hwModel == MeshProtos.HardwareModel.UNSET) null
else hwModel.name.replace('_', '-').replace('p', '.').lowercase()
model = when (val hwModel = rawDeviceMetadata?.hwModel) {
null, MeshProtos.HardwareModel.UNSET -> null
else -> hwModel.name.replace('_', '-').replace('p', '.').lowercase()
},
firmwareVersion = rawDeviceMetadata?.firmwareVersion,
couldUpdate = false,
@ -1506,7 +1507,7 @@ class MeshService : Service(), Logging {
val myInfo = rawMyNodeInfo
val mi = myNodeInfo
if (myInfo != null && mi != null) {
/// Track types of devices and firmware versions in use
// Track types of devices and firmware versions in use
GeeksvilleApplication.analytics.setUserInfo(
DataPair("firmware", mi.firmwareVersion),
DataPair("hw_model", mi.model),
@ -1603,7 +1604,7 @@ class MeshService : Service(), Logging {
}
}
/// If we've received our initial config, our radio settings and all of our channels, send any queued packets and broadcast connected to clients
// If we've received our initial config, our radio settings and all of our channels, send any queued packets and broadcast connected to clients
private fun onHasSettings() {
processQueuedPackets() // send any packets that were queued up
@ -1731,8 +1732,7 @@ class MeshService : Service(), Logging {
}
}
/// Do not use directly, instead call generatePacketId()
// Do not use directly, instead call generatePacketId()
private var currentPacketId = Random(System.currentTimeMillis()).nextLong().absoluteValue
/**
@ -1816,13 +1816,16 @@ class MeshService : Service(), Logging {
info("sendData dest=${p.to}, id=${p.id} <- ${p.bytes!!.size} bytes (connectionState=$connectionState)")
if (p.dataType == 0)
if (p.dataType == 0) {
throw Exception("Port numbers must be non-zero!") // we are now more strict
}
if (p.bytes.size >= MeshProtos.Constants.DATA_PAYLOAD_LEN.number) {
p.status = MessageStatus.ERROR
throw RemoteException("Message too long")
} else p.status = MessageStatus.QUEUED
} else {
p.status = MessageStatus.QUEUED
}
if (connectionState == ConnectionState.CONNECTED) try {
sendNow(p)
@ -1974,7 +1977,7 @@ class MeshService : Service(), Logging {
removeByNodenum = nodeNum
})
}
override fun requestUserInfo( destNum: Int ) = toRemoteExceptions {
override fun requestUserInfo(destNum: Int) = toRemoteExceptions {
if (destNum != myNodeNum) {
sendToRadio(newMeshPacketTo(destNum
).buildMeshPacket(

View file

@ -223,7 +223,7 @@ fun ChannelScreen(
.show()
}
/// Send new channel settings to the device
// Send new channel settings to the device
fun installSettings(
newChannelSet: ChannelSet
) {
@ -420,8 +420,11 @@ fun ChannelScreen(
!isUrlEqual -> stringResource(R.string.send)
else -> "Copy"
},
tint = if (isError) MaterialTheme.colors.error
else LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
tint = if (isError) {
MaterialTheme.colors.error
} else {
LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
}
)
}
},

View file

@ -60,7 +60,8 @@ class DebugFragment : Fragment() {
private val model: DebugViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDebugBinding.inflate(inflater, container, false)

View file

@ -170,7 +170,6 @@ enum class AdminRoute(@StringRes val title: Int) {
SHUTDOWN(R.string.shutdown),
FACTORY_RESET(R.string.factory_reset),
NODEDB_RESET(R.string.nodedb_reset),
;
}
// Config (configType = AdminProtos.AdminMessage.ConfigType)
@ -185,7 +184,6 @@ enum class ConfigRoute(val title: String, val configType: Int = 0) {
LORA("LoRa", 5),
BLUETOOTH("Bluetooth", 6),
SECURITY("Security", configType = 7),
;
}
// ModuleConfig (configType = AdminProtos.AdminMessage.ModuleConfigType)
@ -203,7 +201,6 @@ enum class ModuleRoute(val title: String, val configType: Int = 0) {
AMBIENT_LIGHTING("Ambient Lighting", 10),
DETECTION_SENSOR("Detection Sensor", 11),
PAXCOUNTER("Paxcounter", 12),
;
}
private fun getName(route: Any): String = when (route) {
@ -655,8 +652,11 @@ private fun NavCard(
enabled: Boolean,
onClick: () -> Unit
) {
val color = if (enabled) MaterialTheme.colors.onSurface
else MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
val color = if (enabled) {
MaterialTheme.colors.onSurface
} else {
MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
}
Card(
modifier = Modifier

View file

@ -44,7 +44,7 @@ fun LinkedCoordinates(
tag = "gps",
// URI scheme is defined at:
// https://developer.android.com/guide/components/intents-common#Maps
annotation = "geo:0,0?q=${latitude},${longitude}&z=17&label=${
annotation = "geo:0,0?q=$latitude,$longitude&z=17&label=${
URLEncoder.encode(nodeName, "utf-8")
}"
)

View file

@ -60,7 +60,7 @@ internal fun MessageItem(
) {
val fromLocal = shortName == null
val messageColor = if (fromLocal) R.color.colorMyMsg else R.color.colorMsg
val (topStart, topEnd) = if (fromLocal) 12.dp to 4.dp else 4.dp to 12.dp
val (topStart, topEnd) = if (fromLocal) 12.dp to 4.dp else 4.dp to 12.dp
val messageModifier = if (fromLocal) {
Modifier.padding(start = 48.dp, top = 8.dp, end = 8.dp, bottom = 6.dp)
} else {

View file

@ -104,7 +104,8 @@ class MessagesFragment : Fragment(), Logging {
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = MessagesFragmentBinding.inflate(inflater, container, false)
@ -278,8 +279,9 @@ class MessagesFragment : Fragment(), Logging {
selectedList.forEach {
resendText = resendText + it.text + System.lineSeparator()
}
if (resendText != "")
if (resendText != "") {
resendText = resendText.substring(0, resendText.length - 1)
}
binding.messageInputText.setText(resendText)
mode.finish()
}

View file

@ -90,9 +90,9 @@ fun NodeItem(
}
val (textColor, nodeColor) = thatNode.colors
val hwInfoString = thatNode.user.hwModel.let { hwModel ->
if (hwModel == MeshProtos.HardwareModel.UNSET) MeshProtos.HardwareModel.UNSET.name
else hwModel.name.replace('_', '-').replace('p', '.').lowercase()
val hwInfoString = when (val hwModel = thatNode.user.hwModel) {
MeshProtos.HardwareModel.UNSET -> MeshProtos.HardwareModel.UNSET.name
else -> hwModel.name.replace('_', '-').replace('p', '.').lowercase()
}
val roleName = if (isUnknownUser) {
DeviceConfig.Role.UNRECOGNIZED.name

View file

@ -60,7 +60,8 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat Settings"), Logging
private val model: UIViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = QuickChatSettingsFragmentBinding.inflate(inflater, container, false)
@ -81,11 +82,12 @@ class QuickChatSettingsFragment : ScreenFragment("Quick Chat Settings"), Logging
val name = builder.nameInput.text.toString().trim()
val message = builder.messageInput.text.toString()
if (builder.isNotEmpty())
if (builder.isNotEmpty()) {
model.addQuickChatAction(
name, message,
if (builder.modeSwitch.isChecked) QuickChatAction.Mode.Instant else QuickChatAction.Mode.Append
)
}
}
val dialog = builder.builder.create()

View file

@ -53,7 +53,8 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
private val hasGps by lazy { requireContext().hasGps() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = SettingsFragmentBinding.inflate(inflater, container, false)
@ -86,8 +87,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
debug("current region is $region")
var regionIndex = regions.indexOfFirst { it.regionCode == region }
if (regionIndex == -1) // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset
if (regionIndex == -1) { // Not found, probably because the device has a region our app doesn't yet understand. Punt and say Unset
regionIndex = ConfigProtos.Config.LoRaConfig.RegionCode.UNSET_VALUE
}
// We don't want to be notified of our own changes, so turn off listener while making them
spinner.setSelection(regionIndex, false)
@ -125,7 +127,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
}
override fun onNothingSelected(parent: AdapterView<*>) {
//TODO("Not yet implemented")
// TODO("Not yet implemented")
}
}
@ -248,7 +250,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (view.isPressed) { // We want to ignore changes caused by code (as opposed to the user)
debug("User changed location tracking to $isChecked")
model.provideLocation.value = isChecked
if (isChecked && !view.isChecked)
if (isChecked && !view.isChecked) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.background_required)
.setMessage(R.string.why_background_required)
@ -262,6 +264,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
}
}
.show()
}
}
if (view.isChecked) {
checkLocationEnabled(getString(R.string.location_disabled))
@ -330,8 +333,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
scanModel.showMockInterface()
}
}
if (!device.bonded) // If user just clicked on us, try to bond
if (!device.bonded) { // If user just clicked on us, try to bond
binding.scanStatusText.setText(R.string.starting_pairing)
}
b.isChecked = scanModel.onSelected(device)
}
}
@ -363,10 +367,11 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
devices.values
// Display the device list in alphabetical order while keeping the "None (Disabled)"
// device (fullAddress == n) at the top
.sortedBy { dle -> if (dle.fullAddress == "n") "0" else dle.name }
.sortedBy { dle -> if (dle.fullAddress == "n") "0" else dle.name }
.forEach { device ->
if (device.fullAddress == scanModel.selectedNotNull)
if (device.fullAddress == scanModel.selectedNotNull) {
hasShownOurDevice = true
}
addDeviceButton(device, true)
}
@ -473,8 +478,9 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
if (scanModel.selectedBluetooth) checkBTEnabled()
// Warn user if provide location is selected but location disabled
if (binding.provideLocationCheckbox.isChecked)
if (binding.provideLocationCheckbox.isChecked) {
checkLocationEnabled(getString(R.string.location_disabled))
}
}
override fun onDestroyView() {
@ -486,7 +492,6 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
const val SCAN_PERIOD: Long = 10000 // Stops scanning after 10 seconds
private const val TAP_TRIGGER: Int = 7
private const val TAP_THRESHOLD: Long = 500 // max 500 ms between taps
}
private fun Editable.isIPAddress(): Boolean {
@ -497,5 +502,4 @@ class SettingsFragment : ScreenFragment("Settings"), Logging {
Patterns.IP_ADDRESS.matcher(this).matches()
}
}
}

View file

@ -110,7 +110,6 @@ class UsersFragment : ScreenFragment("Users"), Logging {
parentFragmentManager.navigateToMetrics(nodeNum)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,

View file

@ -37,8 +37,11 @@ fun BitwisePreference(
subtitle = value.toString(),
onClick = { dropDownExpanded = !dropDownExpanded },
enabled = enabled,
trailingIcon = if (dropDownExpanded) Icons.TwoTone.KeyboardArrowUp
else Icons.TwoTone.KeyboardArrowDown,
trailingIcon = if (dropDownExpanded) {
Icons.TwoTone.KeyboardArrowUp
} else {
Icons.TwoTone.KeyboardArrowDown
},
)
Box {

View file

@ -53,7 +53,6 @@ import com.geeksville.mesh.ui.components.CommonCharts.MS_PER_SEC
import com.geeksville.mesh.ui.components.CommonCharts.TIME_FORMAT
import com.geeksville.mesh.ui.theme.Orange
private val DEVICE_METRICS_COLORS = listOf(Color.Green, Color.Magenta, Color.Cyan)
private const val MAX_PERCENT_VALUE = 100f
@ -64,8 +63,9 @@ fun DeviceMetricsScreen(telemetries: List<Telemetry>) {
Column {
if (displayInfoDialog)
if (displayInfoDialog) {
DeviceInfoDialog { displayInfoDialog = false }
}
DeviceMetricsChart(
modifier = Modifier
@ -92,8 +92,7 @@ private fun DeviceMetricsChart(
) {
ChartHeader(amount = telemetries.size)
if (telemetries.isEmpty())
return
if (telemetries.isEmpty()) return
Spacer(modifier = Modifier.height(16.dp))
@ -153,8 +152,9 @@ private fun DeviceMetricsChart(
val x2 = spacing + (i + 1) * spacePerEntry
val y2 = height - spacing - (rightRatio * height)
if (i == 0)
if (i == 0) {
moveTo(x1, y1)
}
lastX = (x1 + x2) / 2f

View file

@ -56,8 +56,11 @@ fun <T> DropDownPreference(
dropDownExpanded = true
},
enabled = enabled,
trailingIcon = if (dropDownExpanded) Icons.TwoTone.KeyboardArrowUp
else Icons.TwoTone.KeyboardArrowDown,
trailingIcon = if (dropDownExpanded) {
Icons.TwoTone.KeyboardArrowUp
} else {
Icons.TwoTone.KeyboardArrowDown
},
summary = summary,
)
@ -74,15 +77,17 @@ fun <T> DropDownPreference(
},
modifier = modifier
.background(
color = if (selectedItem == item.first)
color = if (selectedItem == item.first) {
MaterialTheme.colors.primary.copy(alpha = 0.3f)
else
Color.Unspecified,
} else {
Color.Unspecified
},
),
content = {
Text(
text = item.second,
overflow = TextOverflow.Ellipsis,
maxLines = 1,
)
}
)

View file

@ -21,11 +21,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusState
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
@Composable
fun EditTextPreference(
@ -167,8 +169,12 @@ fun EditTextPreference(
trailingIcon = {
if (trailingIcon != null) {
trailingIcon()
} else {
if (isError) Icon(Icons.TwoTone.Info, "Error", tint = MaterialTheme.colors.error)
} else if (isError) {
Icon(
imageVector = Icons.TwoTone.Info,
contentDescription = stringResource(id = R.string.error),
tint = MaterialTheme.colors.error
)
}
},
)

View file

@ -54,8 +54,6 @@ enum class Iaq(val color: Color, val description: String, val range: IntRange) {
DangerouslyPolluted(Color(0xFF663300), "Dangerously Polluted", 501..Int.MAX_VALUE)
}
fun getIaq(iaq: Int): Iaq {
return when {
iaq in Iaq.Excellent.range -> Iaq.Excellent
@ -70,7 +68,7 @@ fun getIaq(iaq: Int): Iaq {
}
private fun getIaqDescriptionWithRange(iaqEnum: Iaq): String {
return if (iaqEnum.range.last == Int.MAX_VALUE){
return if (iaqEnum.range.last == Int.MAX_VALUE) {
"${iaqEnum.description} (${iaqEnum.range.first}+)"
} else {
"${iaqEnum.description} (${iaqEnum.range.first}-${iaqEnum.range.last})"

View file

@ -31,7 +31,7 @@ fun SimpleAlertDialog(
onClick = onDismiss,
modifier = Modifier
.padding(horizontal = 16.dp),
colors = ButtonDefaults.textButtonColors(
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colors.onSurface,
),
) { Text(text = stringResource(id = R.string.close)) }

View file

@ -39,7 +39,11 @@ fun SwitchPreference(
Text(
text = title,
style = MaterialTheme.typography.body1,
color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified,
color = if (enabled) {
Color.Unspecified
} else {
MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
},
)
Switch(
modifier = modifier

View file

@ -59,8 +59,9 @@ fun BluetoothConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
if (it.toString().length == 6) // ensure 6 digits
if (it.toString().length == 6) { // ensure 6 digits
bluetoothInput = bluetoothInput.copy { fixedPin = it }
}
})
}

View file

@ -187,7 +187,7 @@ fun CannedMessageConfigItemList(
},
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(messagesInput,cannedMessageInput)
onSaveClicked(messagesInput, cannedMessageInput)
}
)
}

View file

@ -78,8 +78,11 @@ private fun ChannelItem(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp)
) {
val textColor = if (enabled) Color.Unspecified
else MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
val textColor = if (enabled) {
Color.Unspecified
} else {
MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
}
Chip(onClick = onClick) {
Text(
@ -165,8 +168,8 @@ fun ChannelSettingsItemList(
}
}
val isEditing: Boolean = settingsList.size != settingsListInput.size
|| settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 }
val isEditing: Boolean = settingsList.size != settingsListInput.size ||
settingsList.zip(settingsListInput).any { (item1, item2) -> item1 != item2 }
var showEditChannelDialog: Int? by rememberSaveable { mutableStateOf(null) }
@ -178,8 +181,11 @@ fun ChannelSettingsItemList(
},
modemPresetName = modemPresetName,
onAddClick = {
if (settingsListInput.size > index) settingsListInput[index] = it
else settingsListInput.add(it)
if (settingsListInput.size > index) {
settingsListInput[index] = it
} else {
settingsListInput.add(it)
}
showEditChannelDialog = null
},
onDismissRequest = { showEditChannelDialog = null }

View file

@ -25,7 +25,7 @@ import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.components.PreferenceFooter
import com.geeksville.mesh.ui.components.SwitchPreference
private val DeviceConfig.Role.stringRes: Int
private val DeviceConfig.Role.stringRes: Int
get() = when (this) {
DeviceConfig.Role.CLIENT -> R.string.role_client
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute

View file

@ -153,7 +153,6 @@ fun DisplayConfigItemList(
)
}
}
}
@Preview(showBackground = true)

View file

@ -68,7 +68,7 @@ fun EditDeviceProfileDialog(
SwitchPreference(
title = field.name,
checked = state[field] == true,
enabled = deviceProfile.hasField(field),
enabled = deviceProfile.hasField(field),
onCheckedChange = { state[field] = it },
padding = PaddingValues(0.dp)
)

View file

@ -140,8 +140,9 @@ fun LoRaConfigItemList(
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onFocusChanged = { isFocused = it.isFocused },
onValueChanged = {
if (it <= loraInput.numChannels) // total num of LoRa channels
if (it <= loraInput.numChannels) { // total num of LoRa channels
loraInput = loraInput.copy { channelNum = it }
}
})
}

View file

@ -97,8 +97,9 @@ fun PositionConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { value ->
if (value >= -90 && value <= 90.0)
if (value >= -90 && value <= 90.0) {
locationInput = locationInput.copy(latitude = value)
}
})
}
item {
@ -107,8 +108,9 @@ fun PositionConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { value ->
if (value >= -180 && value <= 180.0)
if (value >= -180 && value <= 180.0) {
locationInput = locationInput.copy(longitude = value)
}
})
}
item {

View file

@ -58,8 +58,9 @@ fun UserConfigItemList(
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
userInput = userInput.copy { longName = it }
if (getInitials(it).toByteArray().size <= 4) // short_name max_size:5
if (getInitials(it).toByteArray().size <= 4) { // short_name max_size:5
userInput = userInput.copy { shortName = getInitials(it) }
}
})
}

View file

@ -224,7 +224,6 @@ private fun Context.purgeTileSource(onResult: (String) -> Unit) {
} else {
selectedList.remove(i)
}
}
builder.setPositiveButton(R.string.clear) { _, _ ->
for (x in selectedList) {
@ -382,12 +381,13 @@ fun MapView(
debug("User deleted waypoint ${waypoint.id} for me")
model.deleteWaypoint(waypoint.id)
}
if (waypoint.lockedTo in setOf(0, model.myNodeNum ?: 0) && model.isConnected())
if (waypoint.lockedTo in setOf(0, model.myNodeNum ?: 0) && model.isConnected()) {
builder.setPositiveButton(R.string.delete_for_everyone) { _, _ ->
debug("User deleted waypoint ${waypoint.id} for everyone")
model.sendWaypoint(waypoint.copy { expire = 1 })
model.deleteWaypoint(waypoint.id)
}
}
val dialog = builder.show()
for (button in setOf(
androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL,
@ -398,7 +398,7 @@ fun MapView(
fun showMarkerLongPressDialog(id: Int) {
performHapticFeedback()
debug("marker long pressed id=${id}")
debug("marker long pressed id=$id")
val waypoint = waypoints[id]?.data?.waypoint ?: return
// edit only when unlocked or lockedTo myNodeNum
if (waypoint.lockedTo in setOf(0, model.myNodeNum ?: 0) && model.isConnected()) {
@ -488,7 +488,7 @@ fun MapView(
overlays.add(nodeClusterer)
}
addCopyright() // Copyright is required for certain map sources
addCopyright() // Copyright is required for certain map sources
createLatLongGrid(false)
invalidate()
@ -697,12 +697,18 @@ fun MapView(
)
MapButton(
onClick = {
if (context.hasLocationPermission()) map.toggleMyLocation()
else requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
if (context.hasLocationPermission()) {
map.toggleMyLocation()
} else {
requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
}
},
enabled = hasGps,
drawableRes = if (myLocationOverlay == null) R.drawable.ic_twotone_my_location_24
else R.drawable.ic_twotone_location_disabled_24,
drawableRes = if (myLocationOverlay == null) {
R.drawable.ic_twotone_my_location_24
} else {
R.drawable.ic_twotone_location_disabled_24
},
contentDescription = null,
)
}
@ -720,7 +726,6 @@ fun MapView(
expire = Int.MAX_VALUE // TODO add expire picker
lockedTo = if (waypoint.lockedTo != 0) model.myNodeNum ?: 0 else 0
})
},
onDeleteClicked = { waypoint ->
debug("User clicked delete waypoint ${waypoint.id}")

View file

@ -20,7 +20,7 @@ val Any?.anonymize: String
fun Any?.anonymize(maxLen: Int = 3) =
if (this != null) ("..." + this.toString().takeLast(maxLen)) else "null"
/// A toString that makes sure all newlines are removed (for nice logging).
// A toString that makes sure all newlines are removed (for nice logging).
fun Any.toOneLineString() = this.toString().replace('\n', ' ')
fun ConfigProtos.Config.toOneLineString(): String {
@ -30,12 +30,13 @@ fun ConfigProtos.Config.toOneLineString(): String {
.replace('\n', ' ')
}
/// Return a one line string version of an object (but if a release build, just say 'might be PII)
// Return a one line string version of an object (but if a release build, just say 'might be PII)
fun Any.toPIIString() =
if (!BuildConfig.DEBUG)
if (!BuildConfig.DEBUG) {
"<PII?>"
else
} else {
this.toOneLineString()
}
fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
@ -51,7 +52,7 @@ fun formatAgo(lastSeenUnix: Int, currentTimeMillis: Long = System.currentTimeMil
}
}
/// Allows usage like email.onEditorAction(EditorInfo.IME_ACTION_NEXT, { confirm() })
// Allows usage like email.onEditorAction(EditorInfo.IME_ACTION_NEXT, { confirm() })
fun EditText.onEditorAction(actionId: Int, func: () -> Unit) {
setOnEditorActionListener { _, receivedActionId, _ ->

View file

@ -21,8 +21,11 @@ object LanguageUtils : Logging {
fun setLocale(lang: String) {
AppCompatDelegate.setApplicationLocales(
if (lang == SYSTEM_DEFAULT) LocaleListCompat.getEmptyLocaleList()
else LocaleListCompat.forLanguageTags(lang)
if (lang == SYSTEM_DEFAULT) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(lang)
}
)
}
@ -64,4 +67,4 @@ object LanguageUtils : Logging {
}
}
}
}
}