Consistently format byte sizes.

This commit is contained in:
Greyson Parrelli
2025-03-12 15:24:15 -04:00
parent d2006853c7
commit 22d908385b
16 changed files with 31 additions and 124 deletions

View File

@@ -262,7 +262,7 @@ sealed interface BackupStatusData {
class NotEnoughFreeSpace(
requiredSpace: ByteSize
) : BackupStatusData {
val requiredSpace = requiredSpace.toUnitString(maxPlaces = 2)
val requiredSpace = requiredSpace.toUnitString()
override val iconRes: Int = R.drawable.symbol_backup_error_24
@@ -301,8 +301,8 @@ sealed interface BackupStatusData {
@Composable get() = when (restoreStatus) {
RestoreStatus.NORMAL -> stringResource(
R.string.BackupStatus__status_size_of_size,
bytesDownloaded.toUnitString(maxPlaces = 2),
bytesTotal.toUnitString(maxPlaces = 2)
bytesDownloaded.toUnitString(),
bytesTotal.toUnitString()
)
RestoreStatus.LOW_BATTERY -> stringResource(R.string.BackupStatus__status_device_has_low_battery)

View File

@@ -20,6 +20,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.ByteSize;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.database.AttachmentTable;
@@ -115,7 +116,7 @@ public class DocumentView extends FrameLayout {
this.fileName.setText(OptionalUtil.or(documentSlide.getFileName(),
documentSlide.getCaption())
.orElse(getContext().getString(R.string.DocumentView_unnamed_file)));
this.fileSize.setText(Util.getPrettyFileSize(documentSlide.getFileSize()));
this.fileSize.setText(new ByteSize(documentSlide.getFileSize()).toUnitString(2));
this.document.setText(documentSlide.getFileType(getContext()).orElse("").toLowerCase());
this.setOnClickListener(new OpenClickedListener(documentSlide));
}

View File

@@ -103,7 +103,6 @@ import org.thoughtcrime.securesms.keyvalue.protos.ArchiveUploadProgressState
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.util.viewModel
import java.math.BigDecimal
@@ -610,7 +609,7 @@ private fun LazyListScope.appendBackupDetailsItems(
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = Util.getPrettyFileSize(backupMediaSize),
text = backupMediaSize.bytes.toUnitString(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)

View File

@@ -3,13 +3,13 @@ package org.thoughtcrime.securesms.components.settings.app.data
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import androidx.preference.PreferenceManager
import org.signal.core.util.bytes
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate
import org.thoughtcrime.securesms.webrtc.CallDataMode
@@ -46,7 +46,7 @@ class DataAndStorageSettingsFragment : DSLSettingsFragment(R.string.preferences_
return configure {
clickPref(
title = DSLSettingsText.from(R.string.preferences_data_and_storage__manage_storage),
summary = DSLSettingsText.from(Util.getPrettyFileSize(state.totalStorageUse)),
summary = DSLSettingsText.from(state.totalStorageUse.bytes.toUnitString()),
onClick = {
Navigation.findNavController(requireView()).safeNavigate(R.id.action_dataAndStorageSettingsFragment_to_storagePreferenceFragment)
}

View File

@@ -61,6 +61,7 @@ import org.signal.core.ui.Scaffolds
import org.signal.core.ui.SignalPreview
import org.signal.core.ui.Texts
import org.signal.core.ui.theme.SignalTheme
import org.signal.core.util.bytes
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.billing.upgrade.UpgradeToEnableOptimizedStorageSheet
import org.thoughtcrime.securesms.billing.upgrade.UpgradeToPaidTierBottomSheet
@@ -71,7 +72,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity
import org.thoughtcrime.securesms.preferences.widgets.StorageGraphView
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.viewModel
import java.text.NumberFormat
@@ -360,7 +360,7 @@ private fun StorageOverview(
)
it.findViewById<StorageGraphView>(R.id.storageGraphView).setStorageBreakdown(breakdownEntries)
it.findViewById<TextView>(R.id.total_size).text = Util.getPrettyFileSize(breakdownEntries.totalSize)
it.findViewById<TextView>(R.id.total_size).text = breakdownEntries.totalSize.bytes.toUnitString()
}
it.findViewById<View>(R.id.free_up_space).setOnClickListener {

View File

@@ -35,6 +35,7 @@ import com.annimon.stream.Stream;
import com.bumptech.glide.RequestManager;
import com.codewaves.stickyheadergrid.StickyHeaderGridAdapter;
import org.signal.core.util.ByteSize;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.AttachmentId;
@@ -339,7 +340,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
super.bind(context, mediaRecord, slide);
this.slide = slide;
if (showFileSizes | detailView) {
imageFileSize.setText(Util.getPrettyFileSize(slide.getFileSize()));
imageFileSize.setText(new ByteSize(slide.getFileSize()).toUnitString(2));
imageFileSize.setVisibility(View.VISIBLE);
} else {
imageFileSize.setVisibility(View.GONE);
@@ -445,7 +446,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
private String getLine2(@NonNull Context context, @NonNull MediaTable.MediaRecord mediaRecord, @NonNull Slide slide) {
return context.getString(R.string.MediaOverviewActivity_detail_line_3_part,
Util.getPrettyFileSize(slide.getFileSize()),
new ByteSize(slide.getFileSize()).toUnitString(2),
getFileTypeDescription(context, slide),
DateUtils.formatDateWithoutDayOfWeek(Locale.getDefault(), mediaRecord.getDate()));
}

View File

@@ -29,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
import org.signal.core.util.ByteSize;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.concurrent.LifecycleDisposable;
import org.signal.core.util.logging.Log;
@@ -329,7 +330,7 @@ public final class MediaOverviewPageFragment extends Fragment
return getResources().getQuantityString(R.plurals.MediaOverviewActivity_d_selected_s,
mediaCount,
mediaCount,
Util.getPrettyFileSize(totalFileSize));
new ByteSize(totalFileSize).toUnitString());
}
private MediaGalleryAllAdapter getListAdapter() {

View File

@@ -8,12 +8,12 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import org.signal.core.util.bytes
import org.signal.core.util.getParcelableCompat
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.Util
import java.io.IOException
import java.util.Optional
@@ -54,7 +54,7 @@ class MediaSendDocumentFragment : Fragment(R.layout.mediasend_document_fragment)
if (fileInfo != null) {
media.setFileName(fileInfo.first)
name.text = fileInfo.first ?: getString(R.string.DocumentView_unnamed_file)
size.text = Util.getPrettyFileSize(fileInfo.second)
size.text = fileInfo.second.bytes.toUnitString()
val extensionText: String = MediaUtil.getFileType(requireContext(), Optional.ofNullable(fileInfo.first), media.uri).orElse("")
if (extensionText.length <= 3) {

View File

@@ -32,6 +32,7 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.imageview.ShapeableImageView
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.util.bytes
import org.signal.core.util.concurrent.LifecycleDisposable
import org.signal.core.util.concurrent.SimpleTask
import org.signal.core.util.isNotNullOrBlank
@@ -60,7 +61,6 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
import org.thoughtcrime.securesms.util.BottomSheetUtil
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MemoryUnitFormat
import org.thoughtcrime.securesms.util.SystemWindowInsetsSetter
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.fragments.requireListener
@@ -600,7 +600,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
videoSizeHint.text = if (state.isVideoTrimmingVisible) {
val seconds = trimData.getDuration().inWholeSeconds
val bytes = TranscodingQuality.createFromPreset(state.transcodingPreset, trimData.getDuration().inWholeMilliseconds).byteCountEstimate
String.format(Locale.getDefault(), "%d:%02d • %s", seconds / 60, seconds % 60, MemoryUnitFormat.formatBytes(bytes, MemoryUnitFormat.MEGA_BYTES, true))
String.format(Locale.getDefault(), "%d:%02d • %s", seconds / 60, seconds % 60, bytes.bytes.toUnitString())
} else {
null
}

View File

@@ -367,8 +367,8 @@ private fun RestoreProgressDialog(restoreProgress: RestoreV2Event?) {
)
if (restoreProgress != null) {
val progressBytes = restoreProgress.count.toUnitString(maxPlaces = 2)
val totalBytes = restoreProgress.estimatedTotalCount.toUnitString(maxPlaces = 2)
val progressBytes = restoreProgress.count.toUnitString()
val totalBytes = restoreProgress.estimatedTotalCount.toUnitString()
Text(
text = stringResource(id = R.string.RemoteRestoreActivity__s_of_s_s, progressBytes, totalBytes, "%.2f%%".format(restoreProgress.getProgress())),
style = MaterialTheme.typography.bodySmall,

View File

@@ -20,6 +20,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.signal.core.util.bytes
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
@@ -33,7 +34,6 @@ import org.thoughtcrime.securesms.restore.RestoreRepository
import org.thoughtcrime.securesms.restore.RestoreViewModel
import org.thoughtcrime.securesms.util.BackupUtil
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.ViewModelFactory
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.visible
@@ -218,7 +218,7 @@ class RestoreLocalBackupFragment : LoggingFragment(R.layout.fragment_restore_loc
private fun presentBackupFileInfo(backupSize: Long, backupTimestamp: Long) {
if (backupSize > 0) {
binding.backupSizeText.text = getString(R.string.RegistrationActivity_backup_size_s, Util.getPrettyFileSize(backupSize))
binding.backupSizeText.text = getString(R.string.RegistrationActivity_backup_size_s, backupSize.bytes.toUnitString())
}
if (backupTimestamp > 0) {

View File

@@ -18,6 +18,7 @@ import com.pnikosis.materialishprogress.ProgressWheel;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.ByteSize;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.Attachment;
@@ -176,7 +177,7 @@ public class ViewOnceMessageView extends LinearLayout {
if (messageRecord.getSlideDeck().getThumbnailSlide() == null) return "";
long size = messageRecord.getSlideDeck().getThumbnailSlide().getFileSize();
return Util.getPrettyFileSize(size);
return new ByteSize(size).toUnitString(2);
}
private static @StringRes int getDescriptionId(@NonNull MmsMessageRecord messageRecord) {

View File

@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.stories.viewer.info
import android.view.View
import android.widget.TextView
import android.widget.Toast
import org.signal.core.util.bytes
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.Util
@@ -60,7 +61,7 @@ object StoryInfoHeader {
if (model.size > 0L) {
sizeView.visible = true
sizeHeader.visible = true
sizeView.text = Util.getPrettyFileSize(model.size)
sizeView.text = model.size.bytes.toUnitString()
} else {
sizeView.visible = false
sizeHeader.visible = false

View File

@@ -1,58 +0,0 @@
package org.thoughtcrime.securesms.util;
import androidx.annotation.NonNull;
import java.text.DecimalFormat;
/**
* Used for the pretty formatting of bytes for user display.
*/
public enum MemoryUnitFormat {
BYTES(" B"),
KILO_BYTES(" kB"),
MEGA_BYTES(" MB"),
GIGA_BYTES(" GB"),
TERA_BYTES(" TB");
private static final DecimalFormat ONE_DP = new DecimalFormat("#,##0.0");
private static final DecimalFormat OPTIONAL_ONE_DP = new DecimalFormat("#,##0.#");
private final String unitString;
MemoryUnitFormat(String unitString) {
this.unitString = unitString;
}
public double fromBytes(long bytes) {
return bytes / Math.pow(1000, ordinal());
}
/**
* Creates a string suitable to present to the user from the specified {@param bytes}.
* It will pick a suitable unit of measure to display depending on the size of the bytes.
* It will not select a unit of measure lower than the specified {@param minimumUnit}.
*
* @param forceOneDp If true, will include 1 decimal place, even if 0. If false, will only show 1 dp when it's non-zero.
*/
public static String formatBytes(long bytes, @NonNull MemoryUnitFormat minimumUnit, boolean forceOneDp) {
if (bytes <= 0) bytes = 0;
int ordinal = bytes != 0 ? (int) (Math.log10(bytes) / 3) : 0;
if (ordinal >= MemoryUnitFormat.values().length) {
ordinal = MemoryUnitFormat.values().length - 1;
}
MemoryUnitFormat unit = MemoryUnitFormat.values()[ordinal];
if (unit.ordinal() < minimumUnit.ordinal()) {
unit = minimumUnit;
}
return (forceOneDp ? ONE_DP : OPTIONAL_ONE_DP).format(unit.fromBytes(bytes)) + unit.unitString;
}
public static String formatBytes(long bytes) {
return formatBytes(bytes, BYTES, false);
}
}

View File

@@ -187,18 +187,10 @@ public class Util {
return spanned;
}
public static @NonNull String toIsoString(byte[] bytes) {
return new String(bytes, StandardCharsets.ISO_8859_1);
}
public static byte[] toIsoBytes(String isoString) {
return isoString.getBytes(StandardCharsets.ISO_8859_1);
}
public static byte[] toUtf8Bytes(String utf8String) {
return utf8String.getBytes(StandardCharsets.UTF_8);
}
public static void wait(Object lock, long timeout) {
try {
lock.wait(timeout);
@@ -371,10 +363,6 @@ public class Util {
}
}
public static <T> T getRandomElement(T[] elements) {
return elements[new SecureRandom().nextInt(elements.length)];
}
public static <T> T getRandomElement(List<T> elements) {
return elements.get(new SecureRandom().nextInt(elements.size()));
}
@@ -448,38 +436,10 @@ public class Util {
return (int)value;
}
public static boolean isEquals(@Nullable Long first, long second) {
return first != null && first == second;
}
public static String getPrettyFileSize(long sizeBytes) {
return MemoryUnitFormat.formatBytes(sizeBytes);
}
public static void copyToClipboard(@NonNull Context context, @NonNull CharSequence text) {
ServiceUtil.getClipboardManager(context).setPrimaryClip(ClipData.newPlainText(COPY_LABEL, text));
}
@SafeVarargs
public static <T> List<T> concatenatedList(Collection <T>... items) {
final List<T> concat = new ArrayList<>(Stream.of(items).reduce(0, (sum, list) -> sum + list.size()));
for (Collection<T> list : items) {
concat.addAll(list);
}
return concat;
}
public static boolean isLong(String value) {
try {
Long.parseLong(value);
return true;
} catch (NumberFormatException e) {
return false;
}
}
public static int parseInt(String integer, int defaultValue) {
try {
return Integer.parseInt(integer);

View File

@@ -76,7 +76,8 @@ class ByteSize(val bytes: Long) {
}
}
fun toUnitString(maxPlaces: Int = 1, spaced: Boolean = true): String {
@JvmOverloads
fun toUnitString(maxPlaces: Int = 2, spaced: Boolean = true): String {
val (size, unit) = getLargestNonZeroValue()
val formatter = NumberFormat.getInstance().apply {