mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Implement iOS support and unify Compose Multiplatform infrastructure (#4876)
This commit is contained in:
parent
f04924ded5
commit
d136b162a4
170 changed files with 2208 additions and 2432 deletions
|
|
@ -19,12 +19,8 @@ package org.meshtastic.core.model.util
|
|||
import org.meshtastic.core.common.util.nowInstant
|
||||
import org.meshtastic.core.common.util.toDate
|
||||
import org.meshtastic.core.common.util.toInstant
|
||||
import org.meshtastic.core.model.util.TimeConstants.HOURS_PER_DAY
|
||||
import java.text.DateFormat
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
private val DAY_DURATION = 24.hours
|
||||
|
||||
|
|
@ -53,9 +49,3 @@ fun getShortDate(time: Long): String? {
|
|||
* @param remainingMillis The remaining time in milliseconds
|
||||
* @return Pair of (days, hours), where days is Int and hours is Double
|
||||
*/
|
||||
fun formatMuteRemainingTime(remainingMillis: Long): Pair<Int, Double> {
|
||||
val duration = remainingMillis.milliseconds
|
||||
if (duration <= Duration.ZERO) return 0 to 0.0
|
||||
val totalHours = duration.toDouble(DurationUnit.HOURS)
|
||||
return (totalHours / HOURS_PER_DAY).toInt() to (totalHours % HOURS_PER_DAY)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ data class Channel(val settings: ChannelSettings = default.settings, val loraCon
|
|||
cleartextPSK
|
||||
} else {
|
||||
// Treat an index of 1 as the old channelDefaultKey and work up from there
|
||||
val bytes = channelDefaultKey.clone()
|
||||
val bytes = channelDefaultKey.copyOf()
|
||||
bytes[bytes.size - 1] = (0xff and (bytes[bytes.size - 1] + pskIndex - 1)).toByte()
|
||||
bytes.toByteString()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import org.meshtastic.core.common.util.CommonParcel
|
|||
import org.meshtastic.core.common.util.CommonParcelable
|
||||
import org.meshtastic.core.common.util.CommonParcelize
|
||||
import org.meshtastic.core.common.util.CommonTypeParceler
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.model.util.ByteStringParceler
|
||||
import org.meshtastic.core.model.util.ByteStringSerializer
|
||||
|
|
@ -190,7 +191,7 @@ data class DataPacket(
|
|||
// Public-key cryptography (PKC) channel index
|
||||
const val PKC_CHANNEL_INDEX = 8
|
||||
|
||||
fun nodeNumToDefaultId(n: Int): String = "!%08x".format(n)
|
||||
fun nodeNumToDefaultId(n: Int): String = formatString("!%08x", n)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun idToDefaultNodeNum(id: String?): Int? = runCatching { id?.toLong(16)?.toInt() }.getOrNull()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import okio.ByteString
|
|||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.core.common.util.GPSFormat
|
||||
import org.meshtastic.core.common.util.bearing
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.latLongToMeter
|
||||
import org.meshtastic.core.model.util.UnitConversions.celsiusToFahrenheit
|
||||
import org.meshtastic.core.model.util.onlineTimeThreshold
|
||||
|
|
@ -143,20 +144,20 @@ data class Node(
|
|||
val temp =
|
||||
if ((temperature ?: 0f) != 0f) {
|
||||
if (isFahrenheit) {
|
||||
"%.1f°F".format(celsiusToFahrenheit(temperature ?: 0f))
|
||||
formatString("%.1f°F", celsiusToFahrenheit(temperature ?: 0f))
|
||||
} else {
|
||||
"%.1f°C".format(temperature)
|
||||
formatString("%.1f°C", temperature)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val humidity = if ((relative_humidity ?: 0f) != 0f) "%.0f%%".format(relative_humidity) else null
|
||||
val humidity = if ((relative_humidity ?: 0f) != 0f) formatString("%.0f%%", relative_humidity) else null
|
||||
val soilTemperatureStr =
|
||||
if ((soil_temperature ?: 0f) != 0f) {
|
||||
if (isFahrenheit) {
|
||||
"%.1f°F".format(celsiusToFahrenheit(soil_temperature ?: 0f))
|
||||
formatString("%.1f°F", celsiusToFahrenheit(soil_temperature ?: 0f))
|
||||
} else {
|
||||
"%.1f°C".format(soil_temperature)
|
||||
formatString("%.1f°C", soil_temperature)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
|
|
@ -164,12 +165,12 @@ data class Node(
|
|||
val soilMoistureRange = 0..100
|
||||
val soilMoisture =
|
||||
if ((soil_moisture ?: Int.MIN_VALUE) in soilMoistureRange && (soil_temperature ?: 0f) != 0f) {
|
||||
"%d%%".format(soil_moisture)
|
||||
formatString("%d%%", soil_moisture)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val voltage = if ((this.voltage ?: 0f) != 0f) "%.2fV".format(this.voltage) else null
|
||||
val current = if ((current ?: 0f) != 0f) "%.1fmA".format(current) else null
|
||||
val voltage = if ((this.voltage ?: 0f) != 0f) formatString("%.2fV", this.voltage) else null
|
||||
val current = if ((current ?: 0f) != 0f) formatString("%.1fmA", current) else null
|
||||
val iaq = if ((iaq ?: 0) != 0) "IAQ: $iaq" else null
|
||||
|
||||
return listOfNotNull(
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
import org.meshtastic.core.model.util.TimeConstants.HOURS_PER_DAY
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
|
|
@ -46,3 +48,16 @@ fun formatUptime(seconds: Int): String {
|
|||
.joinToString(" ")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the remaining mute time in days and hours.
|
||||
*
|
||||
* @param remainingMillis The remaining time in milliseconds
|
||||
* @return Pair of (days, hours), where days is Int and hours is Double
|
||||
*/
|
||||
fun formatMuteRemainingTime(remainingMillis: Long): Pair<Int, Double> {
|
||||
val duration = remainingMillis.milliseconds
|
||||
if (duration <= kotlin.time.Duration.ZERO) return 0 to 0.0
|
||||
val totalHours = duration.toDouble(kotlin.time.DurationUnit.HOURS)
|
||||
return (totalHours / HOURS_PER_DAY).toInt() to (totalHours % HOURS_PER_DAY)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
package org.meshtastic.core.model.util
|
||||
|
||||
import org.meshtastic.core.common.util.MeasurementSystem
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.getSystemMeasurementSystem
|
||||
import org.meshtastic.proto.Config.DisplayConfig.DisplayUnits
|
||||
|
||||
|
|
@ -49,12 +50,15 @@ fun Int.metersIn(system: DisplayUnits): Float {
|
|||
return this.metersIn(unit)
|
||||
}
|
||||
|
||||
fun Float.toString(unit: DistanceUnit): String = if (unit in setOf(DistanceUnit.METER, DistanceUnit.FOOT)) {
|
||||
"%.0f %s"
|
||||
} else {
|
||||
"%.1f %s"
|
||||
fun Float.toString(unit: DistanceUnit): String {
|
||||
val pattern =
|
||||
if (unit in setOf(DistanceUnit.METER, DistanceUnit.FOOT)) {
|
||||
"%.0f %s"
|
||||
} else {
|
||||
"%.1f %s"
|
||||
}
|
||||
return formatString(pattern, this, unit.symbol)
|
||||
}
|
||||
.format(this, unit.symbol)
|
||||
|
||||
fun Float.toString(system: DisplayUnits): String {
|
||||
val unit =
|
||||
|
|
@ -81,14 +85,14 @@ fun Int.toDistanceString(system: DisplayUnits): String {
|
|||
|
||||
@Suppress("MagicNumber")
|
||||
fun Float.toSpeedString(system: DisplayUnits): String = if (system == DisplayUnits.METRIC) {
|
||||
"%.0f km/h".format(this * 3.6)
|
||||
formatString("%.0f km/h", this * 3.6)
|
||||
} else {
|
||||
"%.0f mph".format(this * 2.23694f)
|
||||
formatString("%.0f mph", this * 2.23694f)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun Float.toSmallDistanceString(system: DisplayUnits): String = if (system == DisplayUnits.IMPERIAL) {
|
||||
"%.2f in".format(this / 25.4f)
|
||||
formatString("%.2f in", this / 25.4f)
|
||||
} else {
|
||||
"%.0f mm".format(this)
|
||||
formatString("%.0f mm", this)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ private fun decodeSharedContactData(data: String): SharedContact {
|
|||
sanitized.decodeBase64() ?: throw IllegalArgumentException("Invalid Base64 string")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
throw MalformedMeshtasticUrlException(
|
||||
"Failed to Base64 decode SharedContact data ($data): ${e.javaClass.simpleName}: ${e.message}",
|
||||
"Failed to Base64 decode SharedContact data ($data): ${e::class.simpleName}: ${e.message}",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ private fun decodeSharedContactData(data: String): SharedContact {
|
|||
SharedContact.ADAPTER.decode(decodedBytes)
|
||||
} catch (e: Exception) {
|
||||
throw MalformedMeshtasticUrlException(
|
||||
"Failed to proto decode SharedContact: ${e.javaClass.simpleName}: ${e.message}",
|
||||
"Failed to proto decode SharedContact: ${e::class.simpleName}: ${e.message}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2026 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.meshtastic.core.model.util
|
||||
|
||||
/** No-op stubs for core:model on iOS. */
|
||||
actual fun getShortDateTime(time: Long): String = ""
|
||||
|
||||
actual fun platformRandomBytes(size: Int): ByteArray = ByteArray(size)
|
||||
|
||||
actual object SfppHasher {
|
||||
actual fun computeMessageHash(encryptedPayload: ByteArray, to: Int, from: Int, id: Int): ByteArray = ByteArray(32)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue