From 5f2f3a00e49e4c08dba2ae2f093e0476a8f012f9 Mon Sep 17 00:00:00 2001 From: Kevin Boulongne Date: Thu, 19 Dec 2024 08:01:38 +0100 Subject: [PATCH] feat: Add `FormatterFileSize` --- .../com/infomaniak/core2/FormatterFileSize.kt | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Core2/src/main/java/com/infomaniak/core2/FormatterFileSize.kt diff --git a/Core2/src/main/java/com/infomaniak/core2/FormatterFileSize.kt b/Core2/src/main/java/com/infomaniak/core2/FormatterFileSize.kt new file mode 100644 index 000000000..65dabb604 --- /dev/null +++ b/Core2/src/main/java/com/infomaniak/core2/FormatterFileSize.kt @@ -0,0 +1,107 @@ +/* + * Infomaniak SwissTransfer - Android + * Copyright (C) 2024 Infomaniak Network SA + * + * 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.infomaniak.core2 + +import android.content.Context +import android.icu.text.DecimalFormat +import android.icu.text.MeasureFormat +import android.icu.text.MeasureFormat.FormatWidth +import android.icu.text.NumberFormat +import android.icu.util.Measure +import android.icu.util.MeasureUnit +import java.math.BigDecimal +import java.util.Locale +import kotlin.math.abs + +object FormatterFileSize { + + private const val KIBI_BYTE = 1_024L + private const val KILO_BYTE = 1_000L + + private const val FLAG_IEC_UNITS = 1 shl 3 + private const val FLAG_SI_UNITS = 1 shl 2 + private const val FLAG_SHORTER = 1 shl 0 + + fun Context.formatShortFileSize(bytes: Long, valueOnly: Boolean = false): String { + return formatFileSize(bytes, FLAG_IEC_UNITS or FLAG_SHORTER, valueOnly) + } + + private fun Context.formatFileSize(bytes: Long, flags: Int, valueOnly: Boolean): String { + + fun getSuffix(suffixes: MutableList): MeasureUnit? { + return if (valueOnly) null else suffixes.removeFirstOrNull() + } + + val suffixes = mutableListOf( + MeasureUnit.BYTE, + MeasureUnit.KILOBYTE, + MeasureUnit.MEGABYTE, + MeasureUnit.GIGABYTE, + MeasureUnit.TERABYTE, + ) + val unit = if (flags and FLAG_IEC_UNITS != 0) KIBI_BYTE else KILO_BYTE + var multiplier = 1L + + var result = abs(bytes).toFloat() + val suffixesCount = suffixes.count() - 1 + var suffix = getSuffix(suffixes) + + repeat(suffixesCount) { + if (result > 900) { + suffix = getSuffix(suffixes) + multiplier *= unit + result /= unit + } + } + + val (roundFormat, roundedBytes) = when { + multiplier == 1L || result >= 100 -> "%.0f" to 0 + result < 1 -> "%.2f" to 2 + result < 10 -> if (flags and FLAG_SHORTER != 0) "%.1f" to 1 else "%.2f" to 2 + else -> if (flags and FLAG_SHORTER != 0) "%.0f" to 0 else "%.2f" to 2 // 10 <= result < 100 + } + + result = abs(result) + + val resultValue = String.format(roundFormat, result) + val resultValueFormatted = suffix?.let { + val locale = currentLocale() + formatMeasureShort(locale, getNumberFormatter(locale, roundedBytes), result, it) + } + + return resultValueFormatted ?: resultValue + } + + private fun Context.currentLocale(): Locale = resources.configuration.locales[0] + + private fun getNumberFormatter(locale: Locale, fractionDigits: Int): NumberFormat { + return NumberFormat.getInstance(locale).apply { + minimumFractionDigits = fractionDigits + maximumFractionDigits = fractionDigits + isGroupingUsed = false + // We do this only for DecimalFormat, since in the general NumberFormat case, + // calling setRoundingMode may throw an exception. + if (this is DecimalFormat) setRoundingMode(BigDecimal.ROUND_HALF_UP) + } + } + + private fun formatMeasureShort(locale: Locale, numberFormatter: NumberFormat, value: Float, units: MeasureUnit): String { + val measureFormatter = MeasureFormat.getInstance(locale, FormatWidth.SHORT, numberFormatter) + return measureFormatter.format(Measure(value, units)) + } +}