diff --git a/commons/build.gradle.kts b/commons/build.gradle.kts index f0a41342e3..794bc27d6a 100644 --- a/commons/build.gradle.kts +++ b/commons/build.gradle.kts @@ -106,6 +106,7 @@ dependencies { implementation(libs.androidx.exifinterface) implementation(libs.androidx.biometric.ktx) implementation(libs.androidx.lifecycle.process) + implementation(libs.androidx.startup) implementation(libs.ez.vcard) implementation(libs.bundles.lifecycle) diff --git a/commons/src/main/AndroidManifest.xml b/commons/src/main/AndroidManifest.xml index 1a2cd43393..27c5310e3f 100644 --- a/commons/src/main/AndroidManifest.xml +++ b/commons/src/main/AndroidManifest.xml @@ -31,6 +31,16 @@ + + + + () private lateinit var backCallback: OnBackPressedCallback + private var useBinaryStorageUnits = false companion object { private const val GENERIC_PERM_HANDLER = 100 @@ -190,6 +191,7 @@ abstract class BaseSimpleActivity : EdgeToEdgeActivity() { installFontInflaterFactory() super.onCreate(savedInstanceState) + useBinaryStorageUnits = baseConfig.useBinaryStorageUnits WindowCompat.enableEdgeToEdge(window) registerBackPressedCallback() @@ -221,6 +223,10 @@ abstract class BaseSimpleActivity : EdgeToEdgeActivity() { override fun onResume() { super.onResume() + if (useBinaryStorageUnits != baseConfig.useBinaryStorageUnits) { + recreate() + return + } if (useDynamicTheme) { setTheme(getThemeId(showTransparentTop = true)) updateBackgroundColor(getProperBackgroundColor()) diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt index b7970cacbf..93c36cfdf6 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Context-styling.kt @@ -149,6 +149,7 @@ fun Context.getPopupMenuTheme(): Int { fun Context.syncGlobalConfig(callback: (() -> Unit)? = null) { if (canAccessGlobalConfig()) { withGlobalConfig { + baseConfig.useBinaryStorageUnits = it?.useBinaryStorageUnits ?: false if (it != null) { baseConfig.apply { showCheckmarksOnSwitches = it.showCheckmarksOnSwitches @@ -182,6 +183,7 @@ fun Context.syncGlobalConfig(callback: (() -> Unit)? = null) { } else { baseConfig.isGlobalThemeEnabled = false baseConfig.showCheckmarksOnSwitches = false + baseConfig.useBinaryStorageUnits = false validateFontSettings() callback?.invoke() } @@ -224,7 +226,8 @@ fun Context.getGlobalConfig(cursorLoader: CursorLoader): GlobalConfig? { showCheckmarksOnSwitches = cursor.getIntValue(MyContentProvider.COL_SHOW_CHECKMARKS_ON_SWITCHES) != 0, lastUpdatedTS = cursor.getIntValue(MyContentProvider.COL_LAST_UPDATED_TS), fontType = cursor.getIntValueOr(MyContentProvider.COL_FONT_TYPE, -1), - fontName = cursor.getStringValueOr(MyContentProvider.COL_FONT_NAME, "") + fontName = cursor.getStringValueOr(MyContentProvider.COL_FONT_NAME, ""), + useBinaryStorageUnits = cursor.getIntValueOr(MyContentProvider.COL_USE_BINARY_STORAGE_UNITS, 0) != 0 ) } catch (_: Exception) { } diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt index 6dc9ce2496..19b43d1cb0 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Int.kt @@ -10,11 +10,8 @@ import androidx.core.graphics.ColorUtils import androidx.core.os.postDelayed import org.fossify.commons.helpers.DARK_GREY import org.fossify.commons.helpers.WCAG_AA_NORMAL -import java.text.DecimalFormat import java.util.Locale import java.util.Random -import kotlin.math.log10 -import kotlin.math.pow fun Int.getContrastColor(): Int { val luminance = ColorUtils.calculateLuminance(this) @@ -49,13 +46,7 @@ fun Int.getFormattedDuration(forceShowHours: Boolean = false): String { } @Deprecated("Use Long.formatSize() instead.") -fun Int.formatSize(): String { - if (this <= 0) return "0 B" - - val units = arrayOf("B", "kB", "MB", "GB", "TB") - val digitGroups = (log10(toDouble()) / log10(1000.0)).toInt() - return "${DecimalFormat("#,##0.#").format(this / 1000.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}" -} +fun Int.formatSize(): String = toLong().formatSize() @Deprecated( message = "Broken due to the Year 2038 problem. Use Long.formatDate() instead (but note that it uses milliseconds, not seconds).", diff --git a/commons/src/main/kotlin/org/fossify/commons/extensions/Long.kt b/commons/src/main/kotlin/org/fossify/commons/extensions/Long.kt index 5a4a11a541..35cbdd010f 100644 --- a/commons/src/main/kotlin/org/fossify/commons/extensions/Long.kt +++ b/commons/src/main/kotlin/org/fossify/commons/extensions/Long.kt @@ -4,19 +4,11 @@ import android.content.Context import android.text.format.DateFormat import android.text.format.DateUtils import android.text.format.Time -import java.text.DecimalFormat +import org.fossify.commons.helpers.FileSizeFormatter import java.util.Calendar import java.util.Locale -import kotlin.math.log10 -import kotlin.math.pow -fun Long.formatSize(): String { - if (this <= 0) return "0 B" - - val units = arrayOf("B", "kB", "MB", "GB", "TB", "PB", "EB") - val digitGroups = (log10(toDouble()) / log10(1000.0)).toInt() - return "${DecimalFormat("#,##0.#").format(this / 1000.0.pow(digitGroups.toDouble()))} ${units[digitGroups]}" -} +fun Long.formatSize(): String = FileSizeFormatter.format(this) fun Long.formatDate(context: Context, dateFormat: String? = null, timeFormat: String? = null): String { val useDateFormat = dateFormat ?: context.baseConfig.dateFormat diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt index c3b9aafe76..9705f35bfd 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/BaseConfig.kt @@ -612,6 +612,12 @@ open class BaseConfig(val context: Context) { var showCheckmarksOnSwitchesFlow = ::showCheckmarksOnSwitches.asFlowNonNull() + var useBinaryStorageUnits: Boolean + get() = prefs.getBoolean(USE_BINARY_STORAGE_UNITS, false) + set(useBinaryStorageUnits) = prefs.edit().putBoolean(USE_BINARY_STORAGE_UNITS, useBinaryStorageUnits).apply() + + val useBinaryStorageUnitsFlow = ::useBinaryStorageUnits.asFlowNonNull() + // Font customization var fontType: Int get() = prefs.getInt(CUSTOM_FONT_TYPE, FONT_TYPE_SYSTEM_DEFAULT) diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt index 62d54b78b4..6bf828631d 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/Constants.kt @@ -203,6 +203,7 @@ const val PASSWORD_COUNTDOWN_START_MS = "password_count_down_start_ms" const val LAST_UNLOCK_TIMESTAMP_MS = "last_unlock_timestamp_ms" const val UNLOCK_TIMEOUT_DURATION_MS = "unlock_timeout_duration_ms" const val SHOW_CHECKMARKS_ON_SWITCHES = "show_checkmarks_on_switches" +const val USE_BINARY_STORAGE_UNITS = "use_binary_storage_units" const val FIRST_DAY_OF_WEEK = "first_day_of_week" const val CUSTOM_FONT_TYPE = "custom_font_type" const val CUSTOM_FONT_FILE_NAME = "custom_font_file_name" diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatter.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatter.kt new file mode 100644 index 0000000000..4d38e856cf --- /dev/null +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatter.kt @@ -0,0 +1,31 @@ +package org.fossify.commons.helpers + +import android.content.Context +import android.content.SharedPreferences +import org.fossify.commons.extensions.getSharedPrefs +import java.text.DecimalFormat +import kotlin.math.log10 +import kotlin.math.pow + +private const val BINARY_UNIT_BASE = 1024.0 + +internal object FileSizeFormatter { + private var preferences: SharedPreferences? = null + private val decimalUnits = arrayOf("B", "kB", "MB", "GB", "TB", "PB", "EB") + private val binaryUnits = arrayOf("B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB") + + fun initialize(context: Context) { + preferences = context.getSharedPrefs() + } + + fun format(bytes: Long): String { + if (bytes <= 0) return "0 B" + + val useBinaryUnits = preferences?.getBoolean(USE_BINARY_STORAGE_UNITS, false) == true + val divisor = if (useBinaryUnits) BINARY_UNIT_BASE else 1000.0 + val units = if (useBinaryUnits) binaryUnits else decimalUnits + val digitGroups = (log10(bytes.toDouble()) / log10(divisor)).toInt() + val value = bytes / divisor.pow(digitGroups.toDouble()) + return "${DecimalFormat("#,##0.#").format(value)} ${units[digitGroups]}" + } +} diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatterInitializer.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatterInitializer.kt new file mode 100644 index 0000000000..0574ee6832 --- /dev/null +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/FileSizeFormatterInitializer.kt @@ -0,0 +1,12 @@ +package org.fossify.commons.helpers + +import android.content.Context +import androidx.startup.Initializer + +class FileSizeFormatterInitializer : Initializer { + override fun create(context: Context) { + FileSizeFormatter.initialize(context) + } + + override fun dependencies(): List>> = emptyList() +} diff --git a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt index a22be4b02a..0d614df27a 100644 --- a/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt +++ b/commons/src/main/kotlin/org/fossify/commons/helpers/MyContentProvider.kt @@ -22,6 +22,7 @@ object MyContentProvider { const val COL_APP_ICON_COLOR = "app_icon_color" const val COL_SHOW_CHECKMARKS_ON_SWITCHES = "show_checkmarks_on_switches" const val COL_LAST_UPDATED_TS = "last_updated_ts" + const val COL_USE_BINARY_STORAGE_UNITS = "use_binary_storage_units" // Font customization const val COL_FONT_TYPE = "font_type" diff --git a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt index 69811429ef..8b6ff394d2 100644 --- a/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt +++ b/commons/src/main/kotlin/org/fossify/commons/models/GlobalConfig.kt @@ -14,6 +14,7 @@ data class GlobalConfig( val lastUpdatedTS: Int = 0, val fontType: Int = FONT_TYPE_SYSTEM_DEFAULT, val fontName: String = "", + val useBinaryStorageUnits: Boolean = false, ) fun GlobalConfig?.isGlobalThemingEnabled(): Boolean { diff --git a/commons/src/main/kotlin/org/fossify/commons/receivers/FossifyBroadcastReceiver.kt b/commons/src/main/kotlin/org/fossify/commons/receivers/FossifyBroadcastReceiver.kt index 699bdbc503..d33e83612e 100644 --- a/commons/src/main/kotlin/org/fossify/commons/receivers/FossifyBroadcastReceiver.kt +++ b/commons/src/main/kotlin/org/fossify/commons/receivers/FossifyBroadcastReceiver.kt @@ -9,7 +9,10 @@ import org.fossify.commons.helpers.MyContentProvider class FossifyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == MyContentProvider.ACTION_GLOBAL_CONFIG_UPDATED) { - context?.syncGlobalConfig() + context?.let { + val pendingResult = goAsync() + it.syncGlobalConfig { pendingResult.finish() } + } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51bf9147b6..f32e3214ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ detektCompose = "0.4.28" androidx-customView = "1.2.0" androidx-customViewPooling = "1.1.0" androidx-lifecycle = "2.10.0" +androidx-startup = "1.2.0" androidx-constraintlayout = "2.2.1" androidx-documentfile = "1.1.0" androidx-biometricKtx = "1.4.0-alpha02" @@ -53,6 +54,7 @@ app-build-javaVersion = "VERSION_17" app-build-kotlinJVMTarget = "17" [libraries] #Android X +androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-biometric-ktx = { module = "androidx.biometric:biometric-ktx", version.ref = "androidx-biometricKtx" } androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }