diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 01b6bd5ff..becaa056e 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -29,12 +29,24 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import com.geeksville.mesh.* +import com.geeksville.mesh.AppOnlyProtos +import com.geeksville.mesh.ChannelProtos import com.geeksville.mesh.ChannelProtos.ChannelSettings import com.geeksville.mesh.ConfigProtos.Config +import com.geeksville.mesh.DataPacket +import com.geeksville.mesh.IMeshService import com.geeksville.mesh.LocalOnlyProtos.LocalConfig import com.geeksville.mesh.LocalOnlyProtos.LocalModuleConfig +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.Portnums +import com.geeksville.mesh.Position +import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging +import com.geeksville.mesh.channel +import com.geeksville.mesh.channelSet +import com.geeksville.mesh.channelSettings +import com.geeksville.mesh.config +import com.geeksville.mesh.copy import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.QuickChatActionRepository @@ -47,6 +59,8 @@ import com.geeksville.mesh.repository.radio.RadioInterfaceService import com.geeksville.mesh.service.MeshService import com.geeksville.mesh.service.ServiceAction import com.geeksville.mesh.ui.map.MAP_STYLE_ID +import com.geeksville.mesh.util.getShortDate +import com.geeksville.mesh.util.getShortDateTime import com.geeksville.mesh.util.positionToMeter import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -67,11 +81,8 @@ import kotlinx.coroutines.withContext import java.io.BufferedWriter import java.io.FileNotFoundException import java.io.FileWriter -import java.text.DateFormat import java.text.SimpleDateFormat -import java.util.Date import java.util.Locale -import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.math.roundToInt @@ -150,30 +161,6 @@ data class Contact( val isMuted: Boolean, ) -// return time if within 24 hours, otherwise date -private fun getShortDate(time: Long): String? { - val date = if (time != 0L) Date(time) else return null - val isWithin24Hours = System.currentTimeMillis() - date.time <= TimeUnit.DAYS.toMillis(1) - - return if (isWithin24Hours) { - DateFormat.getTimeInstance(DateFormat.SHORT).format(date) - } else { - DateFormat.getDateInstance(DateFormat.SHORT).format(date) - } -} - -// return time if within 24 hours, otherwise date/time -private fun getShortDateTime(time: Long): String { - val date = Date(time) - val isWithin24Hours = System.currentTimeMillis() - date.time <= TimeUnit.DAYS.toMillis(1) - - return if (isWithin24Hours) { - DateFormat.getTimeInstance(DateFormat.SHORT).format(date) - } else { - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date) - } -} - @Suppress("LongParameterList") @HiltViewModel class UIViewModel @Inject constructor( diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt index d13e48d83..4a368af1d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshServiceNotifications.kt @@ -36,9 +36,7 @@ import com.geeksville.mesh.TelemetryProtos.LocalStats import com.geeksville.mesh.android.notificationManager import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.util.PendingIntentCompat -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale +import com.geeksville.mesh.util.formatUptime @Suppress("TooManyFunctions") class MeshServiceNotifications( @@ -150,37 +148,29 @@ class MeshServiceNotifications( } } - private fun formatStatsString(stats: LocalStats?, currentStatsUpdatedAtMillis: Long?): String { - val updatedAt = "Next update at: ${ - currentStatsUpdatedAtMillis?.let { - val date = Date(it + FIFTEEN_MINUTES_IN_MILLIS) // Add 15 minutes in milliseconds - val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) - dateFormat.format(date) - } ?: "???" - }" - val statsJoined = stats?.allFields?.mapNotNull { (k, v) -> - if (k.name == "num_online_nodes" || k.name == "num_total_nodes") { - return@mapNotNull null - } - "${ + private fun LocalStats?.formatToString(): String = this?.allFields?.mapNotNull { (k, v) -> + when (k.name) { + "num_online_nodes", "num_total_nodes" -> return@mapNotNull null + "uptime_seconds" -> "Uptime: ${formatUptime(v as Int)}" + "channel_utilization" -> "ChUtil: %.2f%%".format(v) + "air_util_tx" -> "AirUtilTX: %.2f%%".format(v) + else -> "${ k.name.replace('_', ' ').split(" ") .joinToString(" ") { it.replaceFirstChar { char -> char.uppercase() } } - }=$v" - }?.joinToString("\n") ?: "No Local Stats" - return "$updatedAt\n$statsJoined" - } + }: $v" + } + }?.joinToString("\n") ?: "No Local Stats" fun updateServiceStateNotification( summaryString: String? = null, localStats: LocalStats? = null, currentStatsUpdatedAtMillis: Long? = null, ) { - val statsString = formatStatsString(localStats, currentStatsUpdatedAtMillis) notificationManager.notify( notifyId, createServiceStateNotification( name = summaryString.orEmpty(), - message = statsString, + message = localStats.formatToString(), nextUpdateAt = currentStatsUpdatedAtMillis?.plus(FIFTEEN_MINUTES_IN_MILLIS) ) ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt index cc545aa1f..2f61028e5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeDetail.kt @@ -92,8 +92,8 @@ import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider import com.geeksville.mesh.ui.theme.AppTheme import com.geeksville.mesh.util.DistanceUnit import com.geeksville.mesh.util.formatAgo +import com.geeksville.mesh.util.formatUptime import com.geeksville.mesh.util.thenIf -import java.util.concurrent.TimeUnit import kotlin.math.ln @Composable @@ -349,22 +349,6 @@ private fun InfoCard( } } -private fun formatUptime(seconds: Int): String = formatUptime(seconds.toLong()) - -private fun formatUptime(seconds: Long): String { - val days = TimeUnit.SECONDS.toDays(seconds) - val hours = TimeUnit.SECONDS.toHours(seconds) % TimeUnit.DAYS.toHours(1) - val minutes = TimeUnit.SECONDS.toMinutes(seconds) % TimeUnit.HOURS.toMinutes(1) - val secs = seconds % TimeUnit.MINUTES.toSeconds(1) - - return listOfNotNull( - "${days}d".takeIf { days > 0 }, - "${hours}h".takeIf { hours > 0 }, - "${minutes}m".takeIf { minutes > 0 }, - "${secs}s".takeIf { secs > 0 }, - ).joinToString(" ") -} - @OptIn(ExperimentalLayoutApi::class) @Suppress("LongMethod", "CyclomaticComplexMethod") @Composable diff --git a/app/src/main/java/com/geeksville/mesh/util/DateTimeUtils.kt b/app/src/main/java/com/geeksville/mesh/util/DateTimeUtils.kt new file mode 100644 index 000000000..15811f660 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/DateTimeUtils.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 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 . + */ + +package com.geeksville.mesh.util + +import java.text.DateFormat +import java.util.Date +import java.util.concurrent.TimeUnit + +// return time if within 24 hours, otherwise date +fun getShortDate(time: Long): String? { + val date = if (time != 0L) Date(time) else return null + val isWithin24Hours = System.currentTimeMillis() - date.time <= TimeUnit.DAYS.toMillis(1) + + return if (isWithin24Hours) { + DateFormat.getTimeInstance(DateFormat.SHORT).format(date) + } else { + DateFormat.getDateInstance(DateFormat.SHORT).format(date) + } +} + +// return time if within 24 hours, otherwise date/time +fun getShortDateTime(time: Long): String { + val date = Date(time) + val isWithin24Hours = System.currentTimeMillis() - date.time <= TimeUnit.DAYS.toMillis(1) + + return if (isWithin24Hours) { + DateFormat.getTimeInstance(DateFormat.SHORT).format(date) + } else { + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(date) + } +} + +fun formatUptime(seconds: Int): String = formatUptime(seconds.toLong()) + +private fun formatUptime(seconds: Long): String { + val days = TimeUnit.SECONDS.toDays(seconds) + val hours = TimeUnit.SECONDS.toHours(seconds) % TimeUnit.DAYS.toHours(1) + val minutes = TimeUnit.SECONDS.toMinutes(seconds) % TimeUnit.HOURS.toMinutes(1) + val secs = seconds % TimeUnit.MINUTES.toSeconds(1) + + return listOfNotNull( + "${days}d".takeIf { days > 0 }, + "${hours}h".takeIf { hours > 0 }, + "${minutes}m".takeIf { minutes > 0 }, + "${secs}s".takeIf { secs > 0 }, + ).joinToString(" ") +}