diff --git a/app/src/main/java/com/m57/hermescontrol/ui/memory/MemoryScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/memory/MemoryScreen.kt index b85fa06b..a8d9d5ce 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/memory/MemoryScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/memory/MemoryScreen.kt @@ -62,6 +62,7 @@ import com.m57.hermescontrol.ui.common.listContentPadding import com.m57.hermescontrol.ui.common.listItemSpacing import com.m57.hermescontrol.ui.plugins.memoryProviderStatusLabel import com.m57.hermescontrol.ui.plugins.memoryProviderStatusType +import com.m57.hermescontrol.util.formatBytes /** * Memory management home — owns the memory surface that used to live in the @@ -425,10 +426,3 @@ private fun MemoryProviderRow( } } } - -private fun formatBytes(bytes: Long): String = - when { - bytes >= 1024 * 1024 -> "%.1f MB".format(bytes / (1024.0 * 1024.0)) - bytes >= 1024 -> "%.1f KB".format(bytes / 1024.0) - else -> "$bytes B" - } diff --git a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt index 523f862d..e8a36ea1 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/system/SystemScreen.kt @@ -96,31 +96,12 @@ import com.m57.hermescontrol.ui.common.StatusBadgeType import com.m57.hermescontrol.ui.common.ToastEffect import com.m57.hermescontrol.ui.system.components.CredentialEntryRow import com.m57.hermescontrol.ui.system.components.HookCard +import com.m57.hermescontrol.util.formatBytes +import com.m57.hermescontrol.util.formatDuration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -// ── Helpers ───────────────────────────────────────────────────────────── - -private fun formatBytes(bytes: Long): String = - when { - bytes < 1024 -> "$bytes B" - bytes < 1024 * 1024 -> "${bytes / 1024} KB" - bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB" - else -> "${"%.1f".format(bytes.toDouble() / (1024 * 1024 * 1024))} GB" - } - -private fun formatDuration(totalSeconds: Double): String { - val days = (totalSeconds / 86400).toInt() - val hours = ((totalSeconds % 86400) / 3600).toInt() - val minutes = ((totalSeconds % 3600) / 60).toInt() - return buildString { - if (days > 0) append("${days}d ") - if (hours > 0 || days > 0) append("${hours}h ") - append("${minutes}m") - } -} - @OptIn(androidx.compose.foundation.layout.ExperimentalLayoutApi::class) @Composable fun SystemScreen( diff --git a/app/src/main/java/com/m57/hermescontrol/util/FormatUtils.kt b/app/src/main/java/com/m57/hermescontrol/util/FormatUtils.kt new file mode 100644 index 00000000..37cc926f --- /dev/null +++ b/app/src/main/java/com/m57/hermescontrol/util/FormatUtils.kt @@ -0,0 +1,28 @@ +package com.m57.hermescontrol.util + +import java.util.Locale + +/** + * Formats a byte count into human-readable B, KB, MB, or GB. + */ +fun formatBytes(bytes: Long): String = + when { + bytes < 1024 -> "$bytes B" + bytes < 1024 * 1024 -> "${bytes / 1024} KB" + bytes < 1024 * 1024 * 1024 -> "${bytes / (1024 * 1024)} MB" + else -> String.format(Locale.US, "%.1f GB", bytes.toDouble() / (1024 * 1024 * 1024)) + } + +/** + * Formats seconds into a human-readable duration (e.g. "2d 4h 15m"). + */ +fun formatDuration(totalSeconds: Double): String { + val days = (totalSeconds / 86400).toInt() + val hours = ((totalSeconds % 86400) / 3600).toInt() + val minutes = ((totalSeconds % 3600) / 60).toInt() + return buildString { + if (days > 0) append("${days}d ") + if (hours > 0 || days > 0) append("${hours}h ") + append("${minutes}m") + } +} diff --git a/app/src/test/java/com/m57/hermescontrol/util/FormatUtilsTest.kt b/app/src/test/java/com/m57/hermescontrol/util/FormatUtilsTest.kt new file mode 100644 index 00000000..1e2297a5 --- /dev/null +++ b/app/src/test/java/com/m57/hermescontrol/util/FormatUtilsTest.kt @@ -0,0 +1,54 @@ +package com.m57.hermescontrol.util + +import org.junit.Assert.assertEquals +import org.junit.Test + +class FormatUtilsTest { + @Test + fun testFormatBytes_bytes() { + assertEquals("0 B", formatBytes(0L)) + assertEquals("500 B", formatBytes(500L)) + assertEquals("1023 B", formatBytes(1023L)) + } + + @Test + fun testFormatBytes_kilobytes() { + assertEquals("1 KB", formatBytes(1024L)) + assertEquals("2 KB", formatBytes(2048L)) + assertEquals("1023 KB", formatBytes(1024 * 1024 - 1L)) + } + + @Test + fun testFormatBytes_megabytes() { + assertEquals("1 MB", formatBytes(1024 * 1024L)) + assertEquals("500 MB", formatBytes(500 * 1024 * 1024L)) + } + + @Test + fun testFormatBytes_gigabytes() { + assertEquals("1.0 GB", formatBytes(1024 * 1024 * 1024L)) + assertEquals("2.5 GB", formatBytes((2.5 * 1024 * 1024 * 1024).toLong())) + } + + @Test + fun testFormatDuration_minutes() { + assertEquals("0m", formatDuration(0.0)) + assertEquals("0m", formatDuration(45.0)) + assertEquals("1m", formatDuration(60.0)) + assertEquals("59m", formatDuration(3540.0)) + } + + @Test + fun testFormatDuration_hoursAndMinutes() { + assertEquals("1h 0m", formatDuration(3600.0)) + assertEquals("1h 1m", formatDuration(3660.0)) + assertEquals("23h 59m", formatDuration(86399.0)) + } + + @Test + fun testFormatDuration_daysHoursMinutes() { + assertEquals("1d 0h 0m", formatDuration(86400.0)) + assertEquals("1d 1h 1m", formatDuration(90060.0)) + assertEquals("3d 5h 30m", formatDuration(3 * 86400.0 + 5 * 3600.0 + 30 * 60.0)) + } +}