mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
chore: add detekt formatting rule set
https://detekt.dev/docs/next/rules/formatting/
This commit is contained in:
parent
056d4a5829
commit
fe56d257f5
58 changed files with 725 additions and 432 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -29,5 +29,4 @@ interface MeshLogDao {
|
|||
|
||||
@Query("DELETE FROM log")
|
||||
fun deleteAll()
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ class UsersFragment : ScreenFragment("Users"), Logging {
|
|||
parentFragmentManager.navigateToMetrics(nodeNum)
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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})"
|
||||
|
|
|
|||
|
|
@ -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)) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ fun CannedMessageConfigItemList(
|
|||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(messagesInput,cannedMessageInput)
|
||||
onSaveClicked(messagesInput, cannedMessageInput)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ fun DisplayConfigItemList(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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, _ ->
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue