mirror of
https://github.com/amir1376/ab-download-manager.git
synced 2025-02-20 11:43:24 +08:00
add speed unit (#339)
This commit is contained in:
parent
a2077c7e4b
commit
f106c9b702
@ -81,7 +81,7 @@ class AppComponent(
|
||||
DownloadItemOpener,
|
||||
ContainsEffects<AppEffects> by supportEffects(),
|
||||
KoinComponent {
|
||||
private val appRepository: AppRepository by inject()
|
||||
val appRepository: AppRepository by inject()
|
||||
private val appSettings: AppSettingsStorage by inject()
|
||||
private val integration: Integration by inject()
|
||||
|
||||
|
@ -296,7 +296,7 @@ private fun SizeCell(
|
||||
val length by downloadChecker.length.collectAsState()
|
||||
CellText(
|
||||
length?.let {
|
||||
convertSizeToHumanReadable(it).rememberString()
|
||||
convertPositiveSizeToHumanReadable(it, LocalSizeUnit.current).rememberString()
|
||||
} ?: ""
|
||||
)
|
||||
}
|
||||
|
@ -559,7 +559,7 @@ fun RenderFileTypeAndSize(
|
||||
iconModifier
|
||||
)
|
||||
val size = fileInfo.totalLength?.let {
|
||||
convertSizeToHumanReadable(it)
|
||||
convertPositiveSizeToHumanReadable(it, LocalSizeUnit.current)
|
||||
}.takeIf {
|
||||
// this is a length of a html page (error)
|
||||
fileInfo.isSuccessFul
|
||||
|
@ -257,7 +257,9 @@ class AddSingleDownloadComponent(
|
||||
backedBy = speedLimit,
|
||||
describe = {
|
||||
if (it == 0L) Res.string.unlimited.asStringSource()
|
||||
else convertSpeedToHumanReadable(it).asStringSource()
|
||||
else convertPositiveSpeedToHumanReadable(
|
||||
it, appSettings.speedUnit.value
|
||||
).asStringSource()
|
||||
}
|
||||
),
|
||||
IntConfigurable(
|
||||
|
@ -462,7 +462,7 @@ private fun RenderFileTypeAndSize(
|
||||
iconModifier
|
||||
)
|
||||
val size = fileInfo.totalLength?.let {
|
||||
convertSizeToHumanReadable(it)
|
||||
convertPositiveSizeToHumanReadable(it, LocalSizeUnit.current)
|
||||
}.takeIf {
|
||||
// this is a length of a html page (error)
|
||||
fileInfo.isSuccessFul
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.abdownloadmanager.desktop.pages.editdownload
|
||||
|
||||
import com.abdownloadmanager.desktop.repository.AppRepository
|
||||
import com.abdownloadmanager.desktop.utils.*
|
||||
import com.abdownloadmanager.desktop.utils.mvi.ContainsEffects
|
||||
import com.abdownloadmanager.desktop.utils.mvi.supportEffects
|
||||
@ -30,6 +31,7 @@ class EditDownloadComponent(
|
||||
private val downloaderClient: DownloaderClient by inject()
|
||||
val iconProvider: FileIconProvider by inject()
|
||||
val downloadSystem: DownloadSystem by inject()
|
||||
private val appRepository: AppRepository by inject()
|
||||
val editDownloadUiChecker = MutableStateFlow(null as EditDownloadState?)
|
||||
|
||||
init {
|
||||
@ -73,7 +75,8 @@ class EditDownloadComponent(
|
||||
.contains(editedDownloadFile)
|
||||
}
|
||||
},
|
||||
scope,
|
||||
scope = scope,
|
||||
appRepository = appRepository,
|
||||
)
|
||||
editDownloadUiChecker.value = editDownloadState
|
||||
pendingCredential?.let { credentials ->
|
||||
|
@ -3,9 +3,10 @@ package com.abdownloadmanager.desktop.pages.editdownload
|
||||
import com.abdownloadmanager.desktop.pages.settings.configurable.IntConfigurable
|
||||
import com.abdownloadmanager.desktop.pages.settings.configurable.SpeedLimitConfigurable
|
||||
import com.abdownloadmanager.desktop.pages.settings.configurable.StringConfigurable
|
||||
import com.abdownloadmanager.desktop.repository.AppRepository
|
||||
import com.abdownloadmanager.desktop.utils.FileNameValidator
|
||||
import com.abdownloadmanager.desktop.utils.LinkChecker
|
||||
import com.abdownloadmanager.desktop.utils.convertSpeedToHumanReadable
|
||||
import com.abdownloadmanager.desktop.utils.convertPositiveSpeedToHumanReadable
|
||||
import com.abdownloadmanager.resources.Res
|
||||
import com.abdownloadmanager.utils.isValidUrl
|
||||
import ir.amirab.downloader.connection.DownloaderClient
|
||||
@ -16,7 +17,6 @@ import ir.amirab.downloader.downloaditem.withCredentials
|
||||
import ir.amirab.util.compose.StringSource
|
||||
import ir.amirab.util.compose.asStringSource
|
||||
import ir.amirab.util.compose.asStringSourceWithARgs
|
||||
import ir.amirab.util.flow.createMutableStateFlowFromStateFlow
|
||||
import ir.amirab.util.flow.mapStateFlow
|
||||
import ir.amirab.util.flow.mapTwoWayStateFlow
|
||||
import ir.amirab.util.flow.onEachLatest
|
||||
@ -126,6 +126,7 @@ class EditDownloadState(
|
||||
val currentDownloadItem: MutableStateFlow<DownloadItem>,
|
||||
val editedDownloadItem: MutableStateFlow<DownloadItem>,
|
||||
val downloaderClient: DownloaderClient,
|
||||
val appRepository: AppRepository,
|
||||
conflictDetector: DownloadConflictDetector,
|
||||
scope: CoroutineScope,
|
||||
) {
|
||||
@ -165,7 +166,7 @@ class EditDownloadState(
|
||||
),
|
||||
describe = {
|
||||
if (it == 0L) Res.string.unlimited.asStringSource()
|
||||
else convertSpeedToHumanReadable(it).asStringSource()
|
||||
else convertPositiveSpeedToHumanReadable(it, appRepository.speedUnit.value).asStringSource()
|
||||
}
|
||||
),
|
||||
IntConfigurable(
|
||||
|
@ -26,7 +26,6 @@ import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ir.amirab.downloader.utils.ByteConverter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import com.abdownloadmanager.desktop.ui.widget.ActionButton
|
||||
@ -735,14 +734,10 @@ private fun Footer(component: HomeComponent) {
|
||||
val activeCount by component.activeDownloadCountFlow.collectAsState()
|
||||
FooterItem(MyIcons.activeCount, activeCount.toString(), "")
|
||||
val size by component.globalSpeedFlow.collectAsState(0)
|
||||
val speed = baseConvertBytesToHumanReadable(size)
|
||||
val speed = convertPositiveBytesToSizeUnit(size, LocalSpeedUnit.current)
|
||||
if (speed != null) {
|
||||
val speedText = ByteConverter.prettify(speed.value)
|
||||
val unitText = ByteConverter.unitPrettify(speed.unit)
|
||||
?.let {
|
||||
"$it/s"
|
||||
}
|
||||
.orEmpty()
|
||||
val speedText = speed.formatedValue()
|
||||
val unitText = speed.unit.toString() + "/s"
|
||||
FooterItem(MyIcons.speed, speedText, unitText)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.abdownloadmanager.resources.Res
|
||||
import com.abdownloadmanager.resources.*
|
||||
import com.abdownloadmanager.utils.FileIconProvider
|
||||
import com.abdownloadmanager.utils.category.Category
|
||||
import ir.amirab.util.compose.resources.myStringResource
|
||||
@ -175,7 +174,10 @@ fun SpeedCell(
|
||||
(itemState as? ProcessingDownloadItemState)?.speed?.let { remaining ->
|
||||
if (itemState.status == DownloadJobStatus.Downloading) {
|
||||
Text(
|
||||
text = convertSpeedToHumanReadable(remaining),
|
||||
text = convertPositiveSpeedToHumanReadable(
|
||||
remaining,
|
||||
LocalSpeedUnit.current,
|
||||
),
|
||||
maxLines = 1,
|
||||
fontSize = myTextSizes.base,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
@ -190,7 +192,10 @@ fun SizeCell(
|
||||
) {
|
||||
item.contentLength.let {
|
||||
Text(
|
||||
convertSizeToHumanReadable(it).rememberString(),
|
||||
convertPositiveSizeToHumanReadable(
|
||||
it,
|
||||
LocalSizeUnit.current
|
||||
).rememberString(),
|
||||
maxLines = 1,
|
||||
fontSize = myTextSizes.base,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
@ -7,7 +7,7 @@ import com.abdownloadmanager.desktop.storage.AppSettingsStorage
|
||||
import ir.amirab.util.compose.IconSource
|
||||
import com.abdownloadmanager.desktop.ui.icon.MyIcons
|
||||
import com.abdownloadmanager.desktop.utils.BaseComponent
|
||||
import com.abdownloadmanager.desktop.utils.convertSpeedToHumanReadable
|
||||
import com.abdownloadmanager.desktop.utils.convertPositiveSpeedToHumanReadable
|
||||
import com.abdownloadmanager.desktop.utils.mvi.ContainsEffects
|
||||
import com.abdownloadmanager.desktop.utils.mvi.supportEffects
|
||||
import androidx.compose.runtime.*
|
||||
@ -20,6 +20,8 @@ import ir.amirab.util.compose.asStringSource
|
||||
import ir.amirab.util.compose.asStringSourceWithARgs
|
||||
import ir.amirab.util.compose.localizationmanager.LanguageInfo
|
||||
import ir.amirab.util.compose.localizationmanager.LanguageManager
|
||||
import ir.amirab.util.datasize.CommonSizeConvertConfigs
|
||||
import ir.amirab.util.datasize.ConvertSizeConfig
|
||||
import ir.amirab.util.osfileutil.FileUtils
|
||||
import ir.amirab.util.flow.createMutableStateFlowFromStateFlow
|
||||
import ir.amirab.util.flow.mapStateFlow
|
||||
@ -121,6 +123,26 @@ fun trackDeletedFilesOnDisk(appRepository: AppRepository): BooleanConfigurable {
|
||||
)
|
||||
}
|
||||
|
||||
fun speedUnit(appRepository: AppRepository, scope: CoroutineScope): EnumConfigurable<ConvertSizeConfig> {
|
||||
return EnumConfigurable(
|
||||
title = Res.string.settings_download_speed_unit.asStringSource(),
|
||||
description = Res.string.settings_download_speed_unit_description.asStringSource(),
|
||||
backedBy = createMutableStateFlowFromStateFlow(
|
||||
appRepository.speedUnit,
|
||||
updater = { appRepository.setSpeedUnit(it) },
|
||||
scope = scope
|
||||
),
|
||||
possibleValues = listOf(
|
||||
CommonSizeConvertConfigs.BinaryBytes,
|
||||
CommonSizeConvertConfigs.BinaryBits,
|
||||
),
|
||||
describe = {
|
||||
val u = it.baseSize.longString()
|
||||
"$u/s".asStringSource()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun showDownloadFinishWindow(settingsStorage: AppSettingsStorage): BooleanConfigurable {
|
||||
return BooleanConfigurable(
|
||||
title = Res.string.settings_show_completion_dialog.asStringSource(),
|
||||
@ -152,7 +174,7 @@ fun speedLimitConfig(appRepository: AppRepository): SpeedLimitConfigurable {
|
||||
if (it == 0L) {
|
||||
Res.string.unlimited.asStringSource()
|
||||
} else {
|
||||
convertSpeedToHumanReadable(it).asStringSource()
|
||||
convertPositiveSpeedToHumanReadable(it, appRepository.speedUnit.value).asStringSource()
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -398,6 +420,7 @@ class SettingsComponent(
|
||||
uiScaleConfig(appSettings),
|
||||
autoStartConfig(appSettings),
|
||||
mergeTopBarWithTitleBarConfig(appSettings),
|
||||
speedUnit(appRepository, scope),
|
||||
playSoundNotification(appSettings),
|
||||
)
|
||||
|
||||
|
@ -3,7 +3,6 @@ package com.abdownloadmanager.desktop.pages.settings.configurable.widgets
|
||||
import com.abdownloadmanager.desktop.pages.settings.configurable.SpeedLimitConfigurable
|
||||
import com.abdownloadmanager.desktop.ui.widget.CheckBox
|
||||
import com.abdownloadmanager.desktop.ui.widget.DoubleTextField
|
||||
import com.abdownloadmanager.desktop.utils.baseConvertBytesToHumanReadable
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.*
|
||||
import com.abdownloadmanager.desktop.ui.widget.Text
|
||||
@ -11,35 +10,48 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ir.amirab.downloader.utils.ByteConverter
|
||||
import ir.amirab.downloader.utils.ByteConverter.BYTES
|
||||
import ir.amirab.downloader.utils.ByteConverter.K_BYTES
|
||||
import ir.amirab.downloader.utils.ByteConverter.M_BYTES
|
||||
import com.abdownloadmanager.desktop.utils.LocalSpeedUnit
|
||||
import ir.amirab.util.datasize.*
|
||||
|
||||
@Composable
|
||||
fun RenderSpeedConfig(cfg: SpeedLimitConfigurable, modifier: Modifier) {
|
||||
val value by cfg.stateFlow.collectAsState()
|
||||
val setValue = cfg::set
|
||||
val units = listOf(
|
||||
BYTES,
|
||||
K_BYTES,
|
||||
M_BYTES,
|
||||
|
||||
val speedUnit = LocalSpeedUnit.current
|
||||
val allowedFactors = listOf(
|
||||
SizeFactors.FactorValue.Kilo,
|
||||
SizeFactors.FactorValue.Mega,
|
||||
)
|
||||
val enabled= isConfigEnabled()
|
||||
val hasLimitSpeed = value != 0L
|
||||
|
||||
var currentUnit by remember(hasLimitSpeed) { mutableStateOf(baseConvertBytesToHumanReadable(value)?.unit ?: BYTES) }
|
||||
val units = allowedFactors.map {
|
||||
SizeUnit(
|
||||
factorValue = it,
|
||||
baseSize = speedUnit.baseSize,
|
||||
factors = speedUnit.factors
|
||||
)
|
||||
}
|
||||
val enabled = isConfigEnabled()
|
||||
val hasLimitSpeed = value > 0L
|
||||
|
||||
var currentUnit by remember(hasLimitSpeed) {
|
||||
mutableStateOf(
|
||||
SizeConverter.bytesToSize(
|
||||
value,
|
||||
speedUnit.copy(acceptedFactors = allowedFactors)
|
||||
).unit
|
||||
)
|
||||
}
|
||||
var currentValue by remember(value) {
|
||||
val v = ByteConverter.run {
|
||||
prettify(
|
||||
byteTo(value, currentUnit)
|
||||
).toDouble()
|
||||
}
|
||||
val v = SizeConverter.bytesToSize(
|
||||
value, currentUnit.asConverterConfig()
|
||||
).formatedValue().toDouble()
|
||||
mutableStateOf(v)
|
||||
}
|
||||
LaunchedEffect(currentValue, currentUnit) {
|
||||
setValue(
|
||||
ByteConverter.unitToByte(currentValue, currentUnit)
|
||||
SizeConverter.sizeToBytes(
|
||||
SizeWithUnit(currentValue, currentUnit),
|
||||
)
|
||||
)
|
||||
}
|
||||
ConfigTemplate(
|
||||
@ -77,7 +89,7 @@ fun RenderSpeedConfig(cfg: SpeedLimitConfigurable, modifier: Modifier) {
|
||||
}
|
||||
) {
|
||||
val prettified = remember(it) {
|
||||
ByteConverter.unitPrettify(it) + "/s"
|
||||
"$it/s"
|
||||
}
|
||||
Text(prettified)
|
||||
}
|
||||
@ -90,12 +102,22 @@ fun RenderSpeedConfig(cfg: SpeedLimitConfigurable, modifier: Modifier) {
|
||||
value = hasLimitSpeed,
|
||||
enabled = enabled,
|
||||
onValueChange = {
|
||||
if (it) {
|
||||
setValue(ByteConverter.unitToByte(10.0, K_BYTES))
|
||||
} else {
|
||||
setValue(0)
|
||||
}
|
||||
})
|
||||
if (it) {
|
||||
setValue(
|
||||
SizeConverter.sizeToBytes(
|
||||
SizeWithUnit(
|
||||
256.0, SizeUnit(
|
||||
SizeFactors.FactorValue.Kilo,
|
||||
BaseSize.Bytes,
|
||||
SizeFactors.BinarySizeFactors,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
setValue(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
@ -2,14 +2,10 @@ package com.abdownloadmanager.desktop.pages.singleDownloadPage
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.onClick
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.abdownloadmanager.desktop.ui.icon.MyIcons
|
||||
@ -17,7 +13,8 @@ import com.abdownloadmanager.desktop.ui.theme.myColors
|
||||
import com.abdownloadmanager.desktop.ui.theme.myTextSizes
|
||||
import com.abdownloadmanager.desktop.ui.widget.ActionButton
|
||||
import com.abdownloadmanager.desktop.ui.widget.Text
|
||||
import com.abdownloadmanager.desktop.utils.convertSizeToHumanReadable
|
||||
import com.abdownloadmanager.desktop.utils.LocalSizeUnit
|
||||
import com.abdownloadmanager.desktop.utils.convertPositiveSizeToHumanReadable
|
||||
import com.abdownloadmanager.desktop.utils.div
|
||||
import com.abdownloadmanager.resources.Res
|
||||
import com.abdownloadmanager.utils.compose.WithContentColor
|
||||
@ -150,8 +147,10 @@ private fun RenderFileIconAndSize(
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = convertSizeToHumanReadable(itemState.contentLength)
|
||||
.rememberString(),
|
||||
text = convertPositiveSizeToHumanReadable(
|
||||
itemState.contentLength,
|
||||
LocalSizeUnit.current,
|
||||
).rememberString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -342,13 +342,18 @@ fun ColumnScope.RenderPartInfo(itemState: ProcessingDownloadItemState) {
|
||||
}
|
||||
|
||||
PartInfoCells.Downloaded -> {
|
||||
SimpleCellText(convertSizeToHumanReadable(it.value.howMuchProceed).rememberString())
|
||||
SimpleCellText(
|
||||
convertPositiveSizeToHumanReadable(
|
||||
it.value.howMuchProceed,
|
||||
LocalSizeUnit.current
|
||||
).rememberString()
|
||||
)
|
||||
}
|
||||
|
||||
PartInfoCells.Total -> {
|
||||
SimpleCellText(
|
||||
it.value.length?.let { length ->
|
||||
convertSizeToHumanReadable(length).rememberString()
|
||||
convertPositiveSizeToHumanReadable(length, LocalSizeUnit.current).rememberString()
|
||||
} ?: myStringResource(Res.string.unknown),
|
||||
)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.abdownloadmanager.desktop.utils.mvi.ContainsEffects
|
||||
import com.abdownloadmanager.desktop.utils.mvi.supportEffects
|
||||
import arrow.optics.copy
|
||||
import com.abdownloadmanager.desktop.pages.settings.configurable.BooleanConfigurable
|
||||
import com.abdownloadmanager.desktop.repository.AppRepository
|
||||
import com.abdownloadmanager.desktop.storage.AppSettingsStorage
|
||||
import com.abdownloadmanager.desktop.storage.PageStatesStorage
|
||||
import com.abdownloadmanager.resources.Res
|
||||
@ -55,6 +56,7 @@ class SingleDownloadComponent(
|
||||
KoinComponent {
|
||||
private val downloadSystem: DownloadSystem by inject()
|
||||
private val appSettings: AppSettingsStorage by inject()
|
||||
private val appRepository: AppRepository by inject()
|
||||
val fileIconProvider: FileIconProvider by inject()
|
||||
private val singleDownloadPageStateToPersist by lazy {
|
||||
get<PageStatesStorage>().downloadPage
|
||||
@ -122,19 +124,19 @@ class SingleDownloadComponent(
|
||||
add(
|
||||
SingleDownloadPagePropertyItem(
|
||||
Res.string.size.asStringSource(),
|
||||
convertSizeToHumanReadable(it.contentLength)
|
||||
convertPositiveSizeToHumanReadable(it.contentLength, appRepository.sizeUnit.value)
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleDownloadPagePropertyItem(
|
||||
Res.string.download_page_downloaded_size.asStringSource(),
|
||||
convertBytesToHumanReadable(it.progress).orEmpty().asStringSource()
|
||||
convertPositiveSizeToHumanReadable(it.progress, appRepository.sizeUnit.value)
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleDownloadPagePropertyItem(
|
||||
Res.string.speed.asStringSource(),
|
||||
convertSpeedToHumanReadable(it.speed).asStringSource()
|
||||
convertPositiveSpeedToHumanReadable(it.speed, appRepository.speedUnit.value).asStringSource()
|
||||
)
|
||||
)
|
||||
add(
|
||||
@ -331,7 +333,7 @@ class SingleDownloadComponent(
|
||||
if (it == 0L) {
|
||||
Res.string.unlimited.asStringSource()
|
||||
} else {
|
||||
convertSpeedToHumanReadable(it).asStringSource()
|
||||
convertPositiveSpeedToHumanReadable(it, appRepository.speedUnit.value).asStringSource()
|
||||
}
|
||||
},
|
||||
),
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.abdownloadmanager.desktop.repository
|
||||
|
||||
import ir.amirab.util.datasize.CommonSizeConvertConfigs
|
||||
import com.abdownloadmanager.desktop.storage.AppSettingsStorage
|
||||
import com.abdownloadmanager.desktop.utils.AutoStartManager
|
||||
import com.abdownloadmanager.utils.DownloadSystem
|
||||
@ -11,12 +12,12 @@ import com.abdownloadmanager.utils.category.CategoryManager
|
||||
import com.abdownloadmanager.utils.proxy.ProxyManager
|
||||
import ir.amirab.downloader.DownloadManager
|
||||
import ir.amirab.downloader.monitor.IDownloadMonitor
|
||||
import ir.amirab.util.datasize.BaseSize
|
||||
import ir.amirab.util.datasize.ConvertSizeConfig
|
||||
import ir.amirab.util.flow.mapStateFlow
|
||||
import ir.amirab.util.flow.withPrevious
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@ -45,6 +46,20 @@ class AppRepository : KoinComponent {
|
||||
val integrationEnabled = appSettings.browserIntegrationEnabled
|
||||
val integrationPort = appSettings.browserIntegrationPort
|
||||
val trackDeletedFilesOnDisk = appSettings.trackDeletedFilesOnDisk
|
||||
val sizeUnit = MutableStateFlow(
|
||||
CommonSizeConvertConfigs.BinaryBytes
|
||||
)
|
||||
val speedUnit = appSettings.useBitsForSpeed.mapStateFlow { useBits ->
|
||||
if (useBits) {
|
||||
CommonSizeConvertConfigs.BinaryBits
|
||||
} else {
|
||||
CommonSizeConvertConfigs.BinaryBytes
|
||||
}
|
||||
}
|
||||
|
||||
fun setSpeedUnit(speedUnit: ConvertSizeConfig) {
|
||||
appSettings.useBitsForSpeed.value = speedUnit.baseSize == BaseSize.Bits
|
||||
}
|
||||
|
||||
init {
|
||||
saveLocation
|
||||
|
@ -7,6 +7,7 @@ import arrow.optics.optics
|
||||
import com.abdownloadmanager.desktop.App
|
||||
import ir.amirab.util.compose.localizationmanager.LanguageStorage
|
||||
import ir.amirab.util.config.*
|
||||
import ir.amirab.util.datasize.BaseSize
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.component.KoinComponent
|
||||
import java.io.File
|
||||
@ -34,6 +35,7 @@ data class AppSettingsModel(
|
||||
val browserIntegrationEnabled: Boolean = true,
|
||||
val browserIntegrationPort: Int = 15151,
|
||||
val trackDeletedFilesOnDisk: Boolean = false,
|
||||
val useBitsForSpeed: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
val default: AppSettingsModel get() = AppSettingsModel()
|
||||
@ -59,6 +61,7 @@ data class AppSettingsModel(
|
||||
val browserIntegrationEnabled = booleanKeyOf("browserIntegrationEnabled")
|
||||
val browserIntegrationPort = intKeyOf("browserIntegrationPort")
|
||||
val trackDeletedFilesOnDisk = booleanKeyOf("trackDeletedFilesOnDisk")
|
||||
val useBitsForSpeed = booleanKeyOf("useBitsForSpeed")
|
||||
}
|
||||
|
||||
|
||||
@ -87,6 +90,7 @@ data class AppSettingsModel(
|
||||
?: default.browserIntegrationEnabled,
|
||||
browserIntegrationPort = source.get(Keys.browserIntegrationPort) ?: default.browserIntegrationPort,
|
||||
trackDeletedFilesOnDisk = source.get(Keys.trackDeletedFilesOnDisk) ?: default.trackDeletedFilesOnDisk,
|
||||
useBitsForSpeed = source.get(Keys.useBitsForSpeed) ?: default.useBitsForSpeed,
|
||||
)
|
||||
}
|
||||
|
||||
@ -110,6 +114,7 @@ data class AppSettingsModel(
|
||||
put(Keys.browserIntegrationEnabled, focus.browserIntegrationEnabled)
|
||||
put(Keys.browserIntegrationPort, focus.browserIntegrationPort)
|
||||
put(Keys.trackDeletedFilesOnDisk, focus.trackDeletedFilesOnDisk)
|
||||
put(Keys.useBitsForSpeed, focus.useBitsForSpeed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -148,4 +153,5 @@ class AppSettingsStorage(
|
||||
val browserIntegrationEnabled = from(AppSettingsModel.browserIntegrationEnabled)
|
||||
val browserIntegrationPort = from(AppSettingsModel.browserIntegrationPort)
|
||||
val trackDeletedFilesOnDisk = from(AppSettingsModel.trackDeletedFilesOnDisk)
|
||||
val useBitsForSpeed = from(AppSettingsModel.useBitsForSpeed)
|
||||
}
|
@ -14,11 +14,7 @@ import com.abdownloadmanager.desktop.pages.singleDownloadPage.ShowDownloadDialog
|
||||
import com.abdownloadmanager.desktop.ui.icon.MyIcons
|
||||
import com.abdownloadmanager.desktop.ui.theme.ABDownloaderTheme
|
||||
import com.abdownloadmanager.desktop.ui.widget.tray.ComposeTray
|
||||
import com.abdownloadmanager.desktop.utils.AppInfo
|
||||
import com.abdownloadmanager.desktop.utils.GlobalAppExceptionHandler
|
||||
import com.abdownloadmanager.desktop.utils.ProvideGlobalExceptionHandler
|
||||
import ir.amirab.util.compose.action.buildMenu
|
||||
import com.abdownloadmanager.desktop.utils.isInDebugMode
|
||||
import com.abdownloadmanager.desktop.utils.mvi.HandleEffects
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.window.*
|
||||
@ -31,6 +27,7 @@ import com.abdownloadmanager.desktop.pages.home.HomeWindow
|
||||
import com.abdownloadmanager.desktop.pages.settings.ThemeManager
|
||||
import com.abdownloadmanager.desktop.pages.updater.ShowUpdaterDialog
|
||||
import com.abdownloadmanager.desktop.ui.widget.*
|
||||
import com.abdownloadmanager.desktop.utils.*
|
||||
import com.abdownloadmanager.utils.compose.ProvideDebugInfo
|
||||
import ir.amirab.util.compose.localizationmanager.LanguageManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -56,6 +53,7 @@ object Ui : KoinComponent {
|
||||
}
|
||||
application {
|
||||
val theme by themeManager.currentThemeColor.collectAsState()
|
||||
|
||||
ProvideDebugInfo(AppInfo.isInDebugMode()) {
|
||||
ProvideLanguageManager(languageManager) {
|
||||
ProvideNotificationManager {
|
||||
@ -64,39 +62,41 @@ object Ui : KoinComponent {
|
||||
uiScale = appComponent.uiScale.collectAsState().value
|
||||
) {
|
||||
ProvideGlobalExceptionHandler(globalAppExceptionHandler) {
|
||||
val trayState = rememberTrayState()
|
||||
HandleEffectsForApp(appComponent)
|
||||
SystemTray(appComponent, trayState)
|
||||
val showHomeSlot = appComponent.showHomeSlot.collectAsState().value
|
||||
showHomeSlot.child?.instance?.let {
|
||||
HomeWindow(it, appComponent::closeHome)
|
||||
ProvideSizeUnits(appComponent) {
|
||||
val trayState = rememberTrayState()
|
||||
HandleEffectsForApp(appComponent)
|
||||
SystemTray(appComponent, trayState)
|
||||
val showHomeSlot = appComponent.showHomeSlot.collectAsState().value
|
||||
showHomeSlot.child?.instance?.let {
|
||||
HomeWindow(it, appComponent::closeHome)
|
||||
}
|
||||
val showSettingSlot = appComponent.showSettingSlot.collectAsState().value
|
||||
showSettingSlot.child?.instance?.let {
|
||||
SettingWindow(it, appComponent::closeSettings)
|
||||
}
|
||||
val showQueuesSlot = appComponent.showQueuesSlot.collectAsState().value
|
||||
showQueuesSlot.child?.instance?.let {
|
||||
QueuesWindow(it)
|
||||
}
|
||||
val batchDownloadSlot = appComponent.batchDownloadSlot.collectAsState().value
|
||||
batchDownloadSlot.child?.instance?.let {
|
||||
BatchDownloadWindow(it)
|
||||
}
|
||||
val editDownloadSlot = appComponent.editDownloadSlot.collectAsState().value
|
||||
editDownloadSlot.child?.instance?.let {
|
||||
EditDownloadWindow(it)
|
||||
}
|
||||
ShowAddDownloadDialogs(appComponent)
|
||||
ShowDownloadDialogs(appComponent)
|
||||
ShowCategoryDialogs(appComponent)
|
||||
ShowUpdaterDialog(appComponent.updater)
|
||||
ShowAboutDialog(appComponent)
|
||||
NewQueueDialog(appComponent)
|
||||
ShowMessageDialogs(appComponent)
|
||||
ShowOpenSourceLibraries(appComponent)
|
||||
ShowTranslators(appComponent)
|
||||
ConfirmExit(appComponent)
|
||||
}
|
||||
val showSettingSlot = appComponent.showSettingSlot.collectAsState().value
|
||||
showSettingSlot.child?.instance?.let {
|
||||
SettingWindow(it, appComponent::closeSettings)
|
||||
}
|
||||
val showQueuesSlot = appComponent.showQueuesSlot.collectAsState().value
|
||||
showQueuesSlot.child?.instance?.let {
|
||||
QueuesWindow(it)
|
||||
}
|
||||
val batchDownloadSlot = appComponent.batchDownloadSlot.collectAsState().value
|
||||
batchDownloadSlot.child?.instance?.let {
|
||||
BatchDownloadWindow(it)
|
||||
}
|
||||
val editDownloadSlot = appComponent.editDownloadSlot.collectAsState().value
|
||||
editDownloadSlot.child?.instance?.let {
|
||||
EditDownloadWindow(it)
|
||||
}
|
||||
ShowAddDownloadDialogs(appComponent)
|
||||
ShowDownloadDialogs(appComponent)
|
||||
ShowCategoryDialogs(appComponent)
|
||||
ShowUpdaterDialog(appComponent.updater)
|
||||
ShowAboutDialog(appComponent)
|
||||
NewQueueDialog(appComponent)
|
||||
ShowMessageDialogs(appComponent)
|
||||
ShowOpenSourceLibraries(appComponent)
|
||||
ShowTranslators(appComponent)
|
||||
ConfirmExit(appComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,6 +106,18 @@ object Ui : KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProvideSizeUnits(
|
||||
component: AppComponent,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
ProvideSizeAndSpeedUnit(
|
||||
sizeUnitConfig = component.appRepository.sizeUnit.collectAsState().value,
|
||||
speedUnitConfig = component.appRepository.speedUnit.collectAsState().value,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HandleEffectsForApp(appComponent: AppComponent) {
|
||||
val notificationManager = useNotification()
|
||||
|
@ -1,70 +1,64 @@
|
||||
package com.abdownloadmanager.desktop.utils
|
||||
|
||||
import ir.amirab.util.datasize.CommonSizeConvertConfigs
|
||||
import ir.amirab.util.datasize.ConvertSizeConfig
|
||||
import ir.amirab.util.datasize.SizeWithUnit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import com.abdownloadmanager.resources.Res
|
||||
import ir.amirab.downloader.utils.ByteConverter
|
||||
import ir.amirab.util.compose.StringSource
|
||||
import ir.amirab.util.compose.asStringSource
|
||||
import ir.amirab.util.datasize.*
|
||||
|
||||
data class HumanReadableSize(
|
||||
val value:Double,
|
||||
val unit:Long,
|
||||
)
|
||||
|
||||
fun baseConvertBytesToHumanReadable(size: Long):HumanReadableSize?{
|
||||
return ByteConverter.run {
|
||||
when (size) {
|
||||
in Long.MIN_VALUE until 0 -> null
|
||||
|
||||
in 0 until K_BYTES -> {
|
||||
HumanReadableSize(
|
||||
size.toDouble(),
|
||||
BYTES,
|
||||
)
|
||||
}
|
||||
|
||||
in K_BYTES until M_BYTES -> {
|
||||
HumanReadableSize(
|
||||
byteTo(size, K_BYTES),
|
||||
K_BYTES,
|
||||
)
|
||||
}
|
||||
|
||||
in M_BYTES until G_BYTES -> {
|
||||
HumanReadableSize(
|
||||
byteTo(size, M_BYTES),
|
||||
M_BYTES
|
||||
)
|
||||
}
|
||||
|
||||
in G_BYTES..Long.MAX_VALUE -> {
|
||||
HumanReadableSize(
|
||||
byteTo(size, G_BYTES),
|
||||
G_BYTES,
|
||||
)
|
||||
}
|
||||
|
||||
else -> error("should not happened! we covered all range but not this ? $size")
|
||||
}
|
||||
}
|
||||
val LocalSpeedUnit = compositionLocalOf {
|
||||
CommonSizeConvertConfigs.BinaryBytes
|
||||
}
|
||||
fun convertBytesToHumanReadable(size: Long): String? {
|
||||
ByteConverter.run {
|
||||
return baseConvertBytesToHumanReadable(size)?.let {
|
||||
"${prettify(it.value)} ${unitPrettify(it.unit)}"
|
||||
}
|
||||
}
|
||||
val LocalSizeUnit = compositionLocalOf {
|
||||
CommonSizeConvertConfigs.BinaryBytes
|
||||
}
|
||||
|
||||
fun convertSizeToHumanReadable(size: Long): StringSource {
|
||||
return convertBytesToHumanReadable(size)?.asStringSource()
|
||||
@Composable
|
||||
fun ProvideSizeAndSpeedUnit(
|
||||
sizeUnitConfig: ConvertSizeConfig,
|
||||
speedUnitConfig: ConvertSizeConfig,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalSpeedUnit provides speedUnitConfig,
|
||||
LocalSizeUnit provides sizeUnitConfig,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// they are used for ui
|
||||
// size == -1 means that its unknown
|
||||
|
||||
fun convertPositiveBytesToSizeUnit(
|
||||
size: Long,
|
||||
target: ConvertSizeConfig,
|
||||
): SizeWithUnit? {
|
||||
if (size < 0) return null
|
||||
return SizeConverter.bytesToSize(
|
||||
bytes = size,
|
||||
target = target,
|
||||
)
|
||||
}
|
||||
|
||||
fun convertPositiveBytesToHumanReadable(size: Long, target: ConvertSizeConfig): String? {
|
||||
return convertPositiveBytesToSizeUnit(size, target)
|
||||
?.let { "${it.formatedValue()} ${it.unit}" }
|
||||
}
|
||||
|
||||
fun convertPositiveSizeToHumanReadable(size: Long, target: ConvertSizeConfig): StringSource {
|
||||
return convertPositiveBytesToHumanReadable(size, target)
|
||||
?.asStringSource()
|
||||
?: Res.string.unknown.asStringSource()
|
||||
}
|
||||
|
||||
fun convertSpeedToHumanReadable(size: Long, perUnit: String="s"): String {
|
||||
return convertBytesToHumanReadable(size)?.let {
|
||||
"$it/$perUnit"
|
||||
} ?: "-"
|
||||
fun convertPositiveSpeedToHumanReadable(size: Long, target: ConvertSizeConfig, perUnit: String = "s"): String {
|
||||
return convertPositiveBytesToHumanReadable(size, target)
|
||||
?.let { "$it/$perUnit" }
|
||||
?: "-"
|
||||
}
|
||||
//fun main() {
|
||||
// println(convertBytesToHumanReadable(2048000))
|
||||
//}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package ir.amirab.downloader.utils
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.*
|
||||
|
||||
object ByteConverter {
|
||||
const val BYTES = 1L
|
||||
const val K_BYTES = BYTES*1024L
|
||||
const val M_BYTES = K_BYTES * 1024L
|
||||
const val G_BYTES = M_BYTES * 1024L
|
||||
const val T_BYTES = G_BYTES * 1024L
|
||||
private val format = DecimalFormat("#.##", DecimalFormatSymbols(Locale.US))
|
||||
fun byteTo(value: Long, unit: Long): Double {
|
||||
return (value / unit.toDouble())
|
||||
}
|
||||
|
||||
fun unitToByte(value: Double, unit: Long): Long {
|
||||
return (value * unit).toLong()
|
||||
}
|
||||
|
||||
fun prettify(value: Number): String {
|
||||
return format.format(value)
|
||||
}
|
||||
|
||||
fun unitPrettify(unit:Long): String? {
|
||||
return when(unit){
|
||||
BYTES->"B"
|
||||
K_BYTES->"KB"
|
||||
M_BYTES->"MB"
|
||||
G_BYTES->"GB"
|
||||
T_BYTES->"TB"
|
||||
else ->null
|
||||
}
|
||||
}
|
||||
}
|
@ -197,6 +197,8 @@ settings_use_proxy_describe_system_proxy=System Proxy will be used
|
||||
settings_use_proxy_describe_manual_proxy="{{value}}" will be used
|
||||
settings_track_deleted_files_on_disk=Track Deleted Files On Disk
|
||||
settings_track_deleted_files_on_disk_description=Automatically remove files from the list when they are deleted or moved from the download directory.
|
||||
settings_download_speed_unit=Download Speed Unit
|
||||
settings_download_speed_unit_description=Unit used to display the download speed
|
||||
settings_theme=Theme
|
||||
settings_theme_description=Select a theme for the App
|
||||
settings_ui_scale=UI Scale
|
||||
|
@ -0,0 +1,35 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
sealed class BaseSize(
|
||||
val size: Long,
|
||||
) {
|
||||
abstract fun longString(): String
|
||||
fun scaleInto(baseSize: BaseSize): Double {
|
||||
return when {
|
||||
baseSize == this -> 1.0
|
||||
else -> size / baseSize.size.toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
data object Bits : BaseSize(1) {
|
||||
override fun toString(): String {
|
||||
return "b"
|
||||
}
|
||||
|
||||
override fun longString(): String {
|
||||
return "Bits"
|
||||
}
|
||||
}
|
||||
|
||||
data object Bytes : BaseSize(8) {
|
||||
override fun toString(): String {
|
||||
return "B"
|
||||
}
|
||||
|
||||
override fun longString(): String {
|
||||
return "Bytes"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
object CommonSizeConvertConfigs {
|
||||
val BinaryBytes
|
||||
get() = ConvertSizeConfig(
|
||||
baseSize = BaseSize.Bytes,
|
||||
factors = SizeFactors.BinarySizeFactors,
|
||||
)
|
||||
val BinaryBits
|
||||
get() = ConvertSizeConfig(
|
||||
baseSize = BaseSize.Bits,
|
||||
factors = SizeFactors.BinarySizeFactors,
|
||||
)
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
object CommonSizeUnits {
|
||||
val BinaryBytes = SizeUnit(
|
||||
factorValue = SizeFactors.FactorValue.None,
|
||||
baseSize = BaseSize.Bytes,
|
||||
factors = SizeFactors.BinarySizeFactors,
|
||||
)
|
||||
val BinaryBits = SizeUnit(
|
||||
factorValue = SizeFactors.FactorValue.None,
|
||||
baseSize = BaseSize.Bits,
|
||||
factors = SizeFactors.BinarySizeFactors,
|
||||
)
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
data class ConvertSizeConfig(
|
||||
val baseSize: BaseSize,
|
||||
val factors: SizeFactors,
|
||||
// default to auto
|
||||
val acceptedFactors: List<SizeFactors.FactorValue> = SizeFactors.FactorValue.entries,
|
||||
)
|
@ -0,0 +1,50 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
object SizeConverter {
|
||||
fun sizeToBytes(
|
||||
sizeWithUnit: SizeWithUnit,
|
||||
): Long {
|
||||
return convert(
|
||||
sizeWithUnit,
|
||||
CommonSizeConvertConfigs
|
||||
.BinaryBytes
|
||||
.fixedFactor(SizeFactors.FactorValue.None)
|
||||
).value.toLong()
|
||||
}
|
||||
|
||||
fun bytesToSize(
|
||||
bytes: Long,
|
||||
target: ConvertSizeConfig,
|
||||
): SizeWithUnit {
|
||||
return convert(
|
||||
SizeWithUnit(
|
||||
bytes.toDouble(),
|
||||
CommonSizeUnits.BinaryBytes,
|
||||
),
|
||||
target
|
||||
)
|
||||
}
|
||||
|
||||
fun convert(
|
||||
src: SizeWithUnit,
|
||||
target: ConvertSizeConfig,
|
||||
): SizeWithUnit {
|
||||
val valueWithoutFactor = src.unit.factors.removeFactor(
|
||||
src.value, src.unit.factorValue
|
||||
)
|
||||
val valueWithBaseSize = valueWithoutFactor * src.unit.baseSize.scaleInto(target.baseSize)
|
||||
val factorValue = target.factors.bestFactor(
|
||||
valueWithBaseSize.toLong(),
|
||||
target.acceptedFactors,
|
||||
)
|
||||
val finalValue = target.factors.withFactor(valueWithBaseSize, factorValue)
|
||||
return SizeWithUnit(
|
||||
value = finalValue,
|
||||
SizeUnit(
|
||||
factorValue = factorValue,
|
||||
factors = target.factors,
|
||||
baseSize = target.baseSize,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
sealed class SizeFactors(
|
||||
val baseValue: Long,
|
||||
) {
|
||||
enum class FactorValue {
|
||||
None,
|
||||
Kilo,
|
||||
Mega,
|
||||
Giga,
|
||||
Tera,
|
||||
// Peta,
|
||||
// Exa,
|
||||
}
|
||||
|
||||
operator fun get(factorValue: FactorValue): Long {
|
||||
return getFactorSize(factorValue)
|
||||
}
|
||||
|
||||
private fun getFactorSize(factorValue: FactorValue): Long {
|
||||
return factors[factorValue.ordinal]
|
||||
}
|
||||
|
||||
private val factors = FactorValue.entries.map {
|
||||
baseValue.toDouble().pow(it.ordinal).toLong()
|
||||
}
|
||||
|
||||
fun bestFactor(
|
||||
value: Long,
|
||||
acceptedFactors: List<FactorValue> = FactorValue.entries,
|
||||
): FactorValue {
|
||||
require(acceptedFactors.isNotEmpty()) {
|
||||
"acceptedFactors must not be empty"
|
||||
}
|
||||
// we need lowest
|
||||
if (value == 0L) {
|
||||
acceptedFactors.first()
|
||||
}
|
||||
// no other choice
|
||||
if (acceptedFactors.size == 1) return acceptedFactors.first()
|
||||
// find in range
|
||||
val inRange = acceptedFactors.lastOrNull {
|
||||
getFactorSize(it) <= value
|
||||
}
|
||||
if (inRange != null) {
|
||||
return inRange
|
||||
}
|
||||
// find rearrest
|
||||
return acceptedFactors.minBy {
|
||||
(value - getFactorSize(it)).absoluteValue
|
||||
}
|
||||
}
|
||||
|
||||
fun removeFactor(value: Double, factorValue: FactorValue): Long {
|
||||
return (value * getFactorSize(factorValue)).toLong()
|
||||
}
|
||||
|
||||
fun withFactor(value: Double, factorValue: FactorValue): Double {
|
||||
if (factorValue == FactorValue.None) return value
|
||||
return value / getFactorSize(factorValue)
|
||||
}
|
||||
|
||||
abstract fun toString(factorValue: FactorValue): String
|
||||
|
||||
data object DecimalSizeFactors : SizeFactors(baseValue = 1000) {
|
||||
override fun toString(factorValue: FactorValue): String {
|
||||
return when (factorValue) {
|
||||
FactorValue.None -> ""
|
||||
FactorValue.Kilo -> "k"
|
||||
FactorValue.Mega -> "m"
|
||||
FactorValue.Giga -> "g"
|
||||
FactorValue.Tera -> "t"
|
||||
// FactorValue.Peta -> "p"
|
||||
// FactorValue.Exa -> "e"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data object BinarySizeFactors : SizeFactors(baseValue = 1024) {
|
||||
override fun toString(factorValue: FactorValue): String {
|
||||
return when (factorValue) {
|
||||
FactorValue.None -> ""
|
||||
FactorValue.Kilo -> "K"
|
||||
FactorValue.Mega -> "M"
|
||||
FactorValue.Giga -> "G"
|
||||
FactorValue.Tera -> "T"
|
||||
// FactorValue.Peta -> "P"
|
||||
// FactorValue.Exa -> "E"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
data class SizeUnit(
|
||||
val factorValue: SizeFactors.FactorValue = SizeFactors.FactorValue.None,
|
||||
val baseSize: BaseSize,
|
||||
val factors: SizeFactors,
|
||||
) {
|
||||
override fun toString(): String {
|
||||
val factor = factors.toString(factorValue)
|
||||
return "$factor$baseSize"
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.text.NumberFormat
|
||||
import java.util.*
|
||||
|
||||
data class SizeWithUnit(
|
||||
val value: Double,
|
||||
val unit: SizeUnit,
|
||||
) {
|
||||
fun toString(format: NumberFormat?): String {
|
||||
val formattedValue = formatedValue(format)
|
||||
return "$formattedValue $unit"
|
||||
}
|
||||
|
||||
fun formatedValue(format: NumberFormat? = DefaultFormat) = format
|
||||
?.format(value)
|
||||
?: value.toString()
|
||||
|
||||
override fun toString(): String {
|
||||
return toString(DefaultFormat)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DefaultFormat = DecimalFormat(
|
||||
"#.##", DecimalFormatSymbols(
|
||||
Locale.US,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package ir.amirab.util.datasize
|
||||
|
||||
fun SizeUnit.asConverterConfig(
|
||||
acceptedFactors: List<SizeFactors.FactorValue> = listOf(
|
||||
factorValue
|
||||
),
|
||||
): ConvertSizeConfig {
|
||||
return ConvertSizeConfig(
|
||||
factors = factors,
|
||||
baseSize = baseSize,
|
||||
acceptedFactors = acceptedFactors
|
||||
)
|
||||
}
|
||||
|
||||
fun ConvertSizeConfig.bits() = copy(
|
||||
baseSize = BaseSize.Bits
|
||||
)
|
||||
|
||||
fun ConvertSizeConfig.bytes() = copy(
|
||||
baseSize = BaseSize.Bytes
|
||||
)
|
||||
|
||||
fun ConvertSizeConfig.decimal() = copy(
|
||||
factors = SizeFactors.DecimalSizeFactors
|
||||
)
|
||||
|
||||
fun ConvertSizeConfig.binary() = copy(
|
||||
factors = SizeFactors.BinarySizeFactors
|
||||
)
|
||||
|
||||
fun ConvertSizeConfig.autoSelectFactors() = copy(
|
||||
acceptedFactors = SizeFactors.FactorValue.entries
|
||||
)
|
||||
|
||||
fun ConvertSizeConfig.fixedFactor(factorValue: SizeFactors.FactorValue) = copy(
|
||||
acceptedFactors = listOf(factorValue)
|
||||
)
|
Loading…
x
Reference in New Issue
Block a user