Migrate percent to unit module

This commit is contained in:
Julien Papasian 2025-08-17 14:44:44 +02:00
parent 8ab3a158cb
commit 9e4b22e714
92 changed files with 1680 additions and 1090 deletions

View file

@ -107,6 +107,7 @@ import org.breezyweather.unit.precipitation.Precipitation
import org.breezyweather.unit.precipitation.PrecipitationUnit
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.pressure.PressureUnit
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.speed.SpeedUnit
import org.breezyweather.unit.temperature.Temperature
@ -480,10 +481,7 @@ class WeatherContentProvider : ContentProvider() {
relativeHumidity = getPercentUnit(cur.relativeHumidity),
dewPoint = getTemperatureUnit(cur.dewPoint, temperatureUnit),
pressure = getPressureUnit(cur.pressure, pressureUnit),
cloudCover = getPercentUnit(
cur.cloudCover?.toDouble(),
getCloudCoverDescription(context!!, cur.cloudCover)
),
cloudCover = getPercentUnit(cur.cloudCover, cur.cloudCover?.getCloudCoverDescription(context!!)),
visibility = getDistanceUnit(cur.visibility, distanceUnit),
ceiling = getDistanceUnit(cur.ceiling, distanceUnit)
)
@ -562,16 +560,16 @@ class WeatherContentProvider : ContentProvider() {
),
cloudCover = BreezyDailyUnit(
avg = getPercentUnit(
day.cloudCover?.average?.toDouble(),
getCloudCoverDescription(context!!, day.cloudCover?.average)
day.cloudCover?.average,
day.cloudCover?.average?.getCloudCoverDescription(context!!)
),
max = getPercentUnit(
day.cloudCover?.max?.toDouble(),
getCloudCoverDescription(context!!, day.cloudCover?.max)
day.cloudCover?.max,
day.cloudCover?.max?.getCloudCoverDescription(context!!)
),
min = getPercentUnit(
day.cloudCover?.min?.toDouble(),
getCloudCoverDescription(context!!, day.cloudCover?.min)
day.cloudCover?.min,
day.cloudCover?.min?.getCloudCoverDescription(context!!)
),
summary = null
),
@ -609,10 +607,7 @@ class WeatherContentProvider : ContentProvider() {
relativeHumidity = getPercentUnit(hour.relativeHumidity),
dewPoint = getTemperatureUnit(hour.dewPoint, temperatureUnit),
pressure = getPressureUnit(hour.pressure, pressureUnit),
cloudCover = getPercentUnit(
hour.cloudCover?.toDouble(),
getCloudCoverDescription(context!!, hour.cloudCover)
),
cloudCover = getPercentUnit(hour.cloudCover, hour.cloudCover?.getCloudCoverDescription(context!!)),
visibility = getDistanceUnit(hour.visibility, distanceUnit)
)
}
@ -870,12 +865,12 @@ class WeatherContentProvider : ContentProvider() {
}
private fun getPercentUnit(
percent: Double?,
percent: Ratio?,
description: String? = null,
): BreezyUnit? {
return percent?.let {
BreezyUnit(
value = it.roundDecimals(1),
value = it.inPercent.roundDecimals(1),
unit = "percent",
description = description
)

View file

@ -38,6 +38,7 @@ import breezyweather.data.PollenConcentrationColumnAdapter
import breezyweather.data.PollutantConcentrationColumnAdapter
import breezyweather.data.PrecipitationColumnAdapter
import breezyweather.data.PressureColumnAdapter
import breezyweather.data.RatioColumnAdapter
import breezyweather.data.SpeedColumnAdapter
import breezyweather.data.TemperatureColumnAdapter
import breezyweather.data.TimeZoneColumnAdapter
@ -110,9 +111,11 @@ class DbModule {
no2Adapter = PollutantConcentrationColumnAdapter,
o3Adapter = PollutantConcentrationColumnAdapter,
coAdapter = PollutantConcentrationColumnAdapter,
relative_humidityAdapter = RatioColumnAdapter,
dew_pointAdapter = TemperatureColumnAdapter,
pressureAdapter = PressureColumnAdapter,
visibilityAdapter = DistanceColumnAdapter,
cloud_coverAdapter = RatioColumnAdapter,
ceilingAdapter = DistanceColumnAdapter
),
dailysAdapter = Dailys.Adapter(
@ -127,6 +130,11 @@ class DbModule {
daytime_rain_precipitationAdapter = PrecipitationColumnAdapter,
daytime_snow_precipitationAdapter = PrecipitationColumnAdapter,
daytime_ice_precipitationAdapter = PrecipitationColumnAdapter,
daytime_total_precipitation_probabilityAdapter = RatioColumnAdapter,
daytime_thunderstorm_precipitation_probabilityAdapter = RatioColumnAdapter,
daytime_rain_precipitation_probabilityAdapter = RatioColumnAdapter,
daytime_snow_precipitation_probabilityAdapter = RatioColumnAdapter,
daytime_ice_precipitation_probabilityAdapter = RatioColumnAdapter,
daytime_total_precipitation_durationAdapter = DurationColumnAdapter,
daytime_thunderstorm_precipitation_durationAdapter = DurationColumnAdapter,
daytime_rain_precipitation_durationAdapter = DurationColumnAdapter,
@ -145,6 +153,11 @@ class DbModule {
nighttime_rain_precipitationAdapter = PrecipitationColumnAdapter,
nighttime_snow_precipitationAdapter = PrecipitationColumnAdapter,
nighttime_ice_precipitationAdapter = PrecipitationColumnAdapter,
nighttime_total_precipitation_probabilityAdapter = RatioColumnAdapter,
nighttime_thunderstorm_precipitation_probabilityAdapter = RatioColumnAdapter,
nighttime_rain_precipitation_probabilityAdapter = RatioColumnAdapter,
nighttime_snow_precipitation_probabilityAdapter = RatioColumnAdapter,
nighttime_ice_precipitation_probabilityAdapter = RatioColumnAdapter,
nighttime_total_precipitation_durationAdapter = DurationColumnAdapter,
nighttime_thunderstorm_precipitation_durationAdapter = DurationColumnAdapter,
nighttime_rain_precipitation_durationAdapter = DurationColumnAdapter,
@ -182,12 +195,18 @@ class DbModule {
urticaceaeAdapter = PollenConcentrationColumnAdapter,
willowAdapter = PollenConcentrationColumnAdapter,
sunshine_durationAdapter = DurationColumnAdapter,
relative_humidity_averageAdapter = RatioColumnAdapter,
relative_humidity_minAdapter = RatioColumnAdapter,
relative_humidity_maxAdapter = RatioColumnAdapter,
dewpoint_averageAdapter = TemperatureColumnAdapter,
dewpoint_minAdapter = TemperatureColumnAdapter,
dewpoint_maxAdapter = TemperatureColumnAdapter,
pressure_averageAdapter = PressureColumnAdapter,
pressure_maxAdapter = PressureColumnAdapter,
pressure_minAdapter = PressureColumnAdapter,
cloud_cover_averageAdapter = RatioColumnAdapter,
cloud_cover_minAdapter = RatioColumnAdapter,
cloud_cover_maxAdapter = RatioColumnAdapter,
visibility_averageAdapter = DistanceColumnAdapter,
visibility_maxAdapter = DistanceColumnAdapter,
visibility_minAdapter = DistanceColumnAdapter
@ -204,6 +223,11 @@ class DbModule {
rain_precipitationAdapter = PrecipitationColumnAdapter,
snow_precipitationAdapter = PrecipitationColumnAdapter,
ice_precipitationAdapter = PrecipitationColumnAdapter,
total_precipitation_probabilityAdapter = RatioColumnAdapter,
thunderstorm_precipitation_probabilityAdapter = RatioColumnAdapter,
rain_precipitation_probabilityAdapter = RatioColumnAdapter,
snow_precipitation_probabilityAdapter = RatioColumnAdapter,
ice_precipitation_probabilityAdapter = RatioColumnAdapter,
wind_speedAdapter = SpeedColumnAdapter,
wind_gustsAdapter = SpeedColumnAdapter,
pm25Adapter = PollutantConcentrationColumnAdapter,
@ -212,8 +236,10 @@ class DbModule {
no2Adapter = PollutantConcentrationColumnAdapter,
o3Adapter = PollutantConcentrationColumnAdapter,
coAdapter = PollutantConcentrationColumnAdapter,
relative_humidityAdapter = RatioColumnAdapter,
dew_pointAdapter = TemperatureColumnAdapter,
pressureAdapter = PressureColumnAdapter,
cloud_coverAdapter = RatioColumnAdapter,
visibilityAdapter = DistanceColumnAdapter
),
minutelysAdapter = Minutelys.Adapter(

View file

@ -36,9 +36,11 @@ import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.view.animation.OvershootInterpolator
import androidx.annotation.AttrRes
import androidx.annotation.ColorRes
import androidx.annotation.Px
import androidx.annotation.Size
import androidx.annotation.StyleRes
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.createBitmap
import androidx.core.view.WindowInsetsControllerCompat
import com.google.android.material.resources.TextAppearance
@ -205,6 +207,10 @@ fun Context.getThemeColor(
return typedValue.data
}
fun Context.getColorResource(@ColorRes id: Int): androidx.compose.ui.graphics.Color {
return androidx.compose.ui.graphics.Color(ResourcesCompat.getColor(resources, id, theme))
}
@Suppress("DEPRECATION")
fun Window.setSystemBarStyle(
lightStatus: Boolean,

View file

@ -19,6 +19,7 @@ package org.breezyweather.common.extensions
import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import org.breezyweather.R
import org.breezyweather.domain.settings.SettingsManager
import org.breezyweather.unit.distance.Distance
@ -30,9 +31,10 @@ import org.breezyweather.unit.pollutant.PollutantConcentrationUnit
import org.breezyweather.unit.precipitation.Precipitation
import org.breezyweather.unit.precipitation.PrecipitationUnit
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.RatioUnit
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.temperature.Temperature
import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.DurationUnit
@ -357,6 +359,41 @@ fun Duration.formatTime(
)
}
/**
* Convenient format function with parameters filled for our app
*/
fun Ratio.formatPercent(
context: Context,
valueWidth: UnitWidth = UnitWidth.SHORT,
): String {
val settings = SettingsManager.getInstance(context)
return format(
context = context,
unit = RatioUnit.PERCENT,
valueWidth = valueWidth,
locale = context.currentLocale,
useNumberFormatter = settings.useNumberFormatter,
useMeasureFormat = settings.useMeasureFormat
)
}
/**
* Convenient format function with parameters filled for our app
*/
fun Ratio.formatValue(
context: Context,
width: UnitWidth = UnitWidth.SHORT,
): String {
val settings = SettingsManager.getInstance(context)
return formatValue(
unit = RatioUnit.PERCENT,
width = width,
locale = context.currentLocale,
useNumberFormatter = settings.useNumberFormatter,
useMeasureFormat = settings.useMeasureFormat
)
}
// We don't need any cloud cover unit, it's just a percent, but we need some helpers
/**
@ -368,35 +405,25 @@ const val CLOUD_COVER_SCT = 67.5 // 5 okta
const val CLOUD_COVER_BKN = 87.5 // 7 okta
const val CLOUD_COVER_OVC = 100.0 // 8 okta
val cloudCoverScaleThresholds = listOf(
0.0,
CLOUD_COVER_SKC,
CLOUD_COVER_FEW,
CLOUD_COVER_SCT,
CLOUD_COVER_BKN,
CLOUD_COVER_OVC
)
fun Ratio.getCloudCoverColor(context: Context): Int {
return when (inPercent) {
in 0.0..<CLOUD_COVER_FEW -> ContextCompat.getColor(context, R.color.colorLevel_1)
in CLOUD_COVER_FEW..CLOUD_COVER_SCT -> ContextCompat.getColor(context, R.color.colorLevel_2)
in CLOUD_COVER_SCT..100.0 -> ContextCompat.getColor(context, R.color.colorLevel_3)
else -> Color.TRANSPARENT
}
}
/**
* @param context
* @param cloudCover in % (0-100)
*/
fun getCloudCoverDescription(context: Context, cloudCover: Int?): String? {
if (cloudCover == null) return null
return when (cloudCover) {
in 0..<CLOUD_COVER_SKC.roundToInt() -> context.getString(R.string.common_weather_text_clear_sky)
in CLOUD_COVER_SKC.roundToInt()..<CLOUD_COVER_FEW.roundToInt() -> {
context.getString(R.string.common_weather_text_mostly_clear)
}
in CLOUD_COVER_FEW.roundToInt()..<CLOUD_COVER_SCT.roundToInt() -> {
context.getString(R.string.common_weather_text_partly_cloudy)
}
in CLOUD_COVER_SCT.roundToInt()..<CLOUD_COVER_BKN.roundToInt() -> {
context.getString(R.string.common_weather_text_mostly_cloudy)
}
in CLOUD_COVER_BKN.roundToInt()..CLOUD_COVER_OVC.roundToInt() -> {
context.getString(R.string.common_weather_text_cloudy)
}
fun Ratio.getCloudCoverDescription(context: Context): String? {
return when (inPercent) {
in 0.0..<CLOUD_COVER_SKC -> context.getString(R.string.common_weather_text_clear_sky)
in CLOUD_COVER_SKC..<CLOUD_COVER_FEW -> context.getString(R.string.common_weather_text_mostly_clear)
in CLOUD_COVER_FEW..<CLOUD_COVER_SCT -> context.getString(R.string.common_weather_text_partly_cloudy)
in CLOUD_COVER_SCT..<CLOUD_COVER_BKN -> context.getString(R.string.common_weather_text_mostly_cloudy)
in CLOUD_COVER_BKN..CLOUD_COVER_OVC -> context.getString(R.string.common_weather_text_cloudy)
else -> null
}
}

View file

@ -71,19 +71,17 @@ enum class DetailScreen(
(it.uV?.index ?: 0.0) > 0.0
} == true
TAG_HUMIDITY -> location.weather?.dailyForecast?.any {
(it.relativeHumidity?.min ?: 0.0) > 0.0 || (it.relativeHumidity?.max ?: 0.0) > 0.0
it.relativeHumidity?.min != null || it.relativeHumidity?.max != null
} == true ||
location.weather?.hourlyForecast?.any {
(it.relativeHumidity ?: 0.0) > 0.0 || (it.dewPoint ?: 0.0) != 0.0
it.relativeHumidity != null || it.dewPoint != null
} == true
TAG_PRESSURE -> location.weather?.dailyForecast?.any { it.pressure?.average != null } == true ||
location.weather?.hourlyForecast?.any { it.pressure != null } == true
TAG_CLOUD_COVER -> location.weather?.dailyForecast?.any {
(it.cloudCover?.min ?: 0) > 0 || (it.cloudCover?.max ?: 0) > 0
it.cloudCover?.min != null || it.cloudCover?.max != null
} == true ||
location.weather?.hourlyForecast?.any {
(it.cloudCover ?: 0) > 0
} == true
location.weather?.hourlyForecast?.any { it.cloudCover != null } == true
TAG_VISIBILITY -> location.weather?.dailyForecast?.any {
it.visibility?.min != null || it.visibility?.max != null
} == true ||

View file

@ -18,12 +18,6 @@ package org.breezyweather.common.utils
import android.content.Context
import android.content.res.Resources
import android.icu.number.LocalizedNumberFormatter
import android.icu.number.NumberFormatter
import android.icu.number.Precision
import android.icu.text.NumberFormat
import android.icu.util.MeasureUnit
import android.os.Build
import android.text.SpannableString
import android.text.Spanned
import android.text.style.RelativeSizeSpan
@ -116,62 +110,6 @@ object UnitUtils {
)
}
@Deprecated("Use Number.format() extension")
fun formatNumber(
context: Context,
valueWithoutUnit: Number,
precision: Int,
showSign: Boolean = false,
): String {
return valueWithoutUnit.format(
decimals = precision,
locale = context.currentLocale,
showSign = showSign,
useNumberFormatter = SettingsManager.Companion.getInstance(context).useNumberFormatter,
useMeasureFormat = SettingsManager.Companion.getInstance(context).useMeasureFormat
)
}
/**
* Uses LocalizedNumberFormatter on Android SDK >= 30 (which is the recommended way)
* Uses NumberFormat on Android SDK >= 24
* Uses java.text.NumberFormat on Android SDK < 24
*
* @param context
* @param value between 0.0 and 100.0
* @param precision
*/
fun formatPercent(
context: Context,
value: Double,
precision: Int = 0,
): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&
SettingsManager.Companion.getInstance(context).useNumberFormatter
) {
(NumberFormatter.withLocale(context.currentLocale) as LocalizedNumberFormatter)
.precision(if (precision == 0) Precision.integer() else Precision.maxFraction(precision))
.unit(MeasureUnit.PERCENT)
.format(value)
.toString()
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
NumberFormat.getPercentInstance(context.currentLocale)
.apply { maximumFractionDigits = precision }
.format(if (value > 0) value.div(100.0) else 0)
} else {
java.text.NumberFormat.getPercentInstance(context.currentLocale)
.apply { maximumFractionDigits = precision }
.format(if (value > 0) value.div(100.0) else 0)
}
}
fun formatPercent(
context: Context,
value: Int,
): String {
return formatPercent(context, value.toDouble(), 0)
}
/**
* Units will stay at the same size if it somehow fails to parse
*/
@ -263,14 +201,6 @@ object UnitUtils {
}
}
fun validatePercent(percent: Double?): Double? {
return percent?.let { if (it in 0.0..100.0) it else null }
}
fun validatePercent(percent: Int?): Int? {
return percent?.let { if (it in 0..100) it else null }
}
private val ARABIC_DIGITS = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
private val ARABIC_INDIC_DIGITS = charArrayOf('٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩')
private val BENGALI_DIGITS = charArrayOf('', '১', '২', '৩', '', '৫', '৬', '', '৮', '৯')

View file

@ -19,19 +19,20 @@ package org.breezyweather.domain.weather.model
import android.content.Context
import breezyweather.domain.weather.model.DailyCloudCover
import org.breezyweather.R
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.formatValue
import org.breezyweather.common.extensions.getCloudCoverDescription
import org.breezyweather.common.utils.UnitUtils
fun DailyCloudCover.getRangeSummary(context: Context): String? {
return if (min == null || max == null) {
null
} else if (min == max) {
UnitUtils.formatPercent(context, max!!)
max!!.formatPercent(context)
} else {
context.getString(
R.string.cloud_cover_from_to_number,
UnitUtils.formatInt(context, min!!),
UnitUtils.formatPercent(context, max!!)
min!!.formatValue(context),
max!!.formatPercent(context)
)
}
}
@ -40,8 +41,8 @@ fun DailyCloudCover.getRangeDescriptionSummary(context: Context): String? {
return if (min == null || max == null) {
null
} else {
val minDescription = getCloudCoverDescription(context, min)
val maxDescription = getCloudCoverDescription(context, max)
val minDescription = min!!.getCloudCoverDescription(context)
val maxDescription = max!!.getCloudCoverDescription(context)
if (minDescription == maxDescription) {
maxDescription

View file

@ -19,18 +19,19 @@ package org.breezyweather.domain.weather.model
import android.content.Context
import breezyweather.domain.weather.model.DailyRelativeHumidity
import org.breezyweather.R
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.formatValue
fun DailyRelativeHumidity.getRangeSummary(context: Context): String? {
return if (min == null || max == null) {
null
} else if (min == max) {
UnitUtils.formatPercent(context, max!!)
max!!.formatPercent(context)
} else {
context.getString(
R.string.humidity_from_to_number,
UnitUtils.formatDouble(context, min!!),
UnitUtils.formatPercent(context, max!!)
min!!.formatValue(context),
max!!.formatPercent(context)
)
}
}

View file

@ -1,38 +0,0 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package org.breezyweather.domain.weather.model
import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import breezyweather.domain.weather.model.Hourly
import org.breezyweather.R
@ColorInt
fun Hourly.getCloudCoverColor(context: Context): Int {
if (cloudCover == null) return Color.TRANSPARENT
return when (cloudCover!!.toDouble()) {
in 0.0..CLOUD_COVER_CLEAR -> ContextCompat.getColor(context, R.color.colorLevel_1)
in CLOUD_COVER_CLEAR..CLOUD_COVER_PARTLY -> ContextCompat.getColor(context, R.color.colorLevel_2)
in CLOUD_COVER_PARTLY..100.0 -> ContextCompat.getColor(context, R.color.colorLevel_3)
else -> Color.TRANSPARENT
}
}
const val CLOUD_COVER_CLEAR = 37.5
const val CLOUD_COVER_PARTLY = 75.0

View file

@ -59,6 +59,7 @@ import com.google.android.material.textfield.TextInputLayout
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.doOnApplyWindowInsets
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getTabletListAdaptiveWidth
import org.breezyweather.common.extensions.hasPermission
import org.breezyweather.common.extensions.launchUI
@ -68,6 +69,8 @@ import org.breezyweather.common.snackbar.SnackbarManager
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.common.utils.helpers.SnackbarHelper
import org.breezyweather.domain.settings.ConfigStore
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@ -355,7 +358,7 @@ abstract class AbstractWidgetConfigActivity : BreezyActivity() {
valueTo = 100f
value = ((cardAlpha.toDouble() / 10.0).roundToInt() * 10.0).toFloat()
setLabelFormatter { value: Float ->
UnitUtils.formatPercent(context, value.toDouble())
value.toDouble().percent.formatPercent(context, UnitWidth.NARROW)
}
addOnChangeListener { _, value, _ ->
if (cardAlpha != value.roundToInt()) {
@ -418,7 +421,7 @@ abstract class AbstractWidgetConfigActivity : BreezyActivity() {
}
}
setLabelFormatter { value: Float ->
UnitUtils.formatPercent(context, value.toDouble())
value.toDouble().percent.formatPercent(context, UnitWidth.NARROW)
}
}
@ -480,6 +483,14 @@ abstract class AbstractWidgetConfigActivity : BreezyActivity() {
}
mBottomSheetScrollView = findViewById(R.id.activity_widget_config_custom_scrollView)
mSubtitleInputLayout = findViewById(R.id.activity_widget_config_subtitle_inputLayout)
mSubtitleInputLayout?.setEndIconOnClickListener {
val message = getString(R.string.widget_custom_subtitle_explanation) + "\n\n" + subtitleCustomKeywords
MaterialAlertDialogBuilder(this)
.setTitle(R.string.widget_custom_subtitle_alert_box_title)
.setMessage(message)
.setPositiveButton(R.string.action_done, null)
.show()
}
mSubtitleEditText = findViewById<TextInputEditText>(R.id.activity_widget_config_subtitle_inputter).apply {
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
@ -509,15 +520,6 @@ abstract class AbstractWidgetConfigActivity : BreezyActivity() {
mBottomSheetBehavior!!.peekHeight = mSubtitleInputLayout!!.measuredHeight
setBottomSheetState(isCustomSubtitle)
}
val subtitleInputLayout = findViewById<TextInputLayout>(R.id.activity_widget_config_subtitle_inputLayout)
subtitleInputLayout.setEndIconOnClickListener {
val message = getString(R.string.widget_custom_subtitle_explanation) + "\n\n" + subtitleCustomKeywords
MaterialAlertDialogBuilder(this)
.setTitle(R.string.widget_custom_subtitle_alert_box_title)
.setMessage(message)
.setPositiveButton(R.string.action_done, null)
.show()
}
}
abstract fun updateWidgetView()

View file

@ -33,6 +33,7 @@ import breezyweather.domain.location.model.Location
import breezyweather.domain.weather.model.Weather
import org.breezyweather.R
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getFormattedDate
import org.breezyweather.common.extensions.getFormattedMediumDayAndMonth
import org.breezyweather.common.extensions.getFormattedMediumDayAndMonthInAdditionalCalendar
@ -317,9 +318,8 @@ abstract class AbstractRemoteViewsPresenter {
?: context.getString(R.string.null_data_text)
).replace(
"\$ch$",
weather.current?.relativeHumidity?.let {
UnitUtils.formatPercent(context, it)
} ?: context.getString(R.string.null_data_text)
weather.current?.relativeHumidity?.formatPercent(context, UnitWidth.NARROW)
?: context.getString(R.string.null_data_text)
).replace(
"\$cps$",
weather.current?.pressure?.formatMeasure(context)
@ -466,14 +466,14 @@ abstract class AbstractRemoteViewsPresenter {
) ?: context.getString(R.string.null_data_text)
).replace(
"$" + i + "dp$",
weather.dailyForecastStartingToday.getOrNull(i)?.day?.precipitationProbability?.total?.let {
UnitUtils.formatPercent(context, it)
} ?: context.getString(R.string.null_data_text)
weather.dailyForecastStartingToday.getOrNull(i)?.day?.precipitationProbability?.total
?.formatPercent(context, UnitWidth.NARROW)
?: context.getString(R.string.null_data_text)
).replace(
"$" + i + "np$",
weather.dailyForecastStartingToday.getOrNull(i)?.night?.precipitationProbability?.total?.let {
UnitUtils.formatPercent(context, it)
} ?: context.getString(R.string.null_data_text)
weather.dailyForecastStartingToday.getOrNull(i)?.night?.precipitationProbability?.total
?.formatPercent(context, UnitWidth.NARROW)
?: context.getString(R.string.null_data_text)
).replace(
"$" + i + "dwd$",
weather.dailyForecastStartingToday.getOrNull(i)?.day?.wind?.getShortDescription(context)

View file

@ -29,6 +29,7 @@ import breezyweather.domain.weather.model.Weather
import org.breezyweather.R
import org.breezyweather.background.receiver.widget.WidgetClockDayDetailsProvider
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getFormattedMediumDayAndMonthInAdditionalCalendar
import org.breezyweather.common.extensions.getShortWeekdayDayMonth
import org.breezyweather.common.options.appearance.CalendarHelper
@ -288,7 +289,7 @@ object ClockDayDetailsWidgetIMP : AbstractRemoteViewsPresenter() {
weather.current?.relativeHumidity?.let {
context.getString(R.string.humidity) +
context.getString(R.string.colon_separator) +
UnitUtils.formatPercent(context, it)
it.formatPercent(context, UnitWidth.NARROW)
}
}
}

View file

@ -34,6 +34,7 @@ import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.background.receiver.widget.WidgetTrendDailyProvider
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getCalendarMonth
import org.breezyweather.common.extensions.getFormattedShortDayAndMonth
import org.breezyweather.common.extensions.getTabletListAdaptiveWidth
@ -217,12 +218,10 @@ object DailyTrendWidgetIMP : AbstractRemoteViewsPresenter() {
ResourceHelper.getWidgetNotificationIcon(provider, it, true, minimalIcon, lightTheme)
)
}
val daytimePrecipitationProbability = daily.day?.precipitationProbability?.total?.toFloat()
val nighttimePrecipitationProbability = daily.night?.precipitationProbability?.total?.toFloat()
val p = max(
daytimePrecipitationProbability ?: 0f,
nighttimePrecipitationProbability ?: 0f
)
val daytimePrecipitationProbability = daily.day?.precipitationProbability?.total
val nighttimePrecipitationProbability = daily.night?.precipitationProbability?.total
val p = listOfNotNull(daytimePrecipitationProbability, nighttimePrecipitationProbability)
.takeIf { it.isNotEmpty() }?.maxBy { it.value }
widgetItemView.trendItemView.setData(
buildTemperatureArrayForItem(daytimeTemperatures, i),
buildTemperatureArrayForItem(nighttimeTemperatures, i),
@ -238,12 +237,8 @@ object DailyTrendWidgetIMP : AbstractRemoteViewsPresenter() {
),
highestTemperature,
lowestTemperature,
if (p > 0) p else null,
if (p > 0) {
UnitUtils.formatPercent(context, p.toDouble())
} else {
null
},
p?.takeIf { it.value > 0 }?.inPercent?.toFloat(),
p?.takeIf { it.value > 0 }?.formatPercent(context, UnitWidth.NARROW),
100f,
0f
)

View file

@ -45,7 +45,6 @@ import org.breezyweather.common.extensions.CLOUD_COVER_FEW
import org.breezyweather.common.extensions.ensurePositive
import org.breezyweather.common.extensions.getIsoFormattedDate
import org.breezyweather.common.extensions.toCalendarWithTimeZone
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.domain.weather.index.PollutantIndex
import org.breezyweather.domain.weather.model.validate
import org.breezyweather.ui.theme.weatherView.WeatherViewController
@ -58,6 +57,10 @@ import org.breezyweather.unit.precipitation.Precipitation.Companion.micrometers
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.pressure.Pressure.Companion.pascals
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.ratio.Ratio.Companion.permille
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
@ -224,13 +227,13 @@ internal fun computeMissingHourlyData(
ice = hourly.precipitation!!.ice?.toValidHourlyOrNull()
)
val precipitationProbability = hourly.precipitationProbability?.copy(
total = UnitUtils.validatePercent(hourly.precipitationProbability!!.total),
thunderstorm = UnitUtils.validatePercent(hourly.precipitationProbability!!.thunderstorm),
rain = UnitUtils.validatePercent(hourly.precipitationProbability!!.rain),
snow = UnitUtils.validatePercent(hourly.precipitationProbability!!.snow),
ice = UnitUtils.validatePercent(hourly.precipitationProbability!!.ice)
total = hourly.precipitationProbability!!.total?.toValidRangeOrNull(),
thunderstorm = hourly.precipitationProbability!!.thunderstorm?.toValidRangeOrNull(),
rain = hourly.precipitationProbability!!.rain?.toValidRangeOrNull(),
snow = hourly.precipitationProbability!!.snow?.toValidRangeOrNull(),
ice = hourly.precipitationProbability!!.ice?.toValidRangeOrNull()
)
val cloudCover = UnitUtils.validatePercent(hourly.cloudCover)
val cloudCover = hourly.cloudCover?.toValidRangeOrNull()
val visibility = hourly.visibility?.toValidOrNull()
val weatherCode = hourly.weatherCode ?: getHalfDayWeatherCodeFromHourlyList(
listOf(hourly.toHourly()),
@ -240,7 +243,7 @@ internal fun computeMissingHourlyData(
cloudCover,
visibility
)
val relativeHumidity = UnitUtils.validatePercent(hourly.relativeHumidity)
val relativeHumidity = hourly.relativeHumidity?.toValidRangeOrNull()
?: computeRelativeHumidity(temp, hourly.dewPoint?.toValidOrNull())
val dewPoint = hourly.dewPoint?.toValidOrNull()
?: computeDewPoint(temp, relativeHumidity)
@ -279,16 +282,16 @@ internal fun computeMissingHourlyData(
private fun computeRelativeHumidity(
temperature: Temperature?,
dewPoint: Temperature?,
): Double? {
): Ratio? {
if (temperature == null || dewPoint == null) return null
val b = if (temperature < 0.celsius) 17.966 else 17.368
val c = if (temperature < 0.celsius) 227.15 else 238.88 // °C
return 100 * (
return (
exp((b * dewPoint.inCelsius).div(c + dewPoint.inCelsius)) /
exp((b * temperature.inCelsius).div(c + temperature.inCelsius))
)
).fraction
}
/**
@ -297,18 +300,18 @@ private fun computeRelativeHumidity(
* TODO: Unit test
*
* @param temperature
* @param relativeHumidity in %
* @param relativeHumidity
*/
private fun computeDewPoint(
temperature: Temperature?,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
): Temperature? {
if (temperature == null || relativeHumidity == null) return null
val b = if (temperature < 0.celsius) 17.966 else 17.368
val c = if (temperature < 0.celsius) 227.15 else 238.88 // °C
val magnus = ln(relativeHumidity / 100) + (b * temperature.inCelsius) / (c + temperature.inCelsius)
val magnus = ln(relativeHumidity.inFraction) + (b * temperature.inCelsius) / (c + temperature.inCelsius)
return ((c * magnus) / (b - magnus)).celsius
}
@ -319,16 +322,17 @@ private fun computeDewPoint(
* TODO: Unit test
*
* @param temperature
* @param relativeHumidity in %
* @param relativeHumidity
* @param windSpeed
*/
internal fun computeApparentTemperature(
temperature: Temperature?,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
windSpeed: Speed?,
): Temperature? {
if (temperature == null || relativeHumidity == null || windSpeed == null) return null
val e = relativeHumidity.div(100) * 6.105 * exp(17.27 * temperature.inCelsius / (237.7 + temperature.inCelsius))
val e = relativeHumidity.inFraction * 6.105 * exp(17.27 * temperature.inCelsius / (237.7 + temperature.inCelsius))
return (temperature.inCelsius + 0.33 * e - 0.7 * windSpeed.inMetersPerSecond - 4.0).celsius
}
@ -878,11 +882,11 @@ private fun completeHalfDayFromHourlyList(
}
val initialPrecipitationProbability = newHalfDay.precipitationProbability?.copy(
total = UnitUtils.validatePercent(newHalfDay.precipitationProbability!!.total),
thunderstorm = UnitUtils.validatePercent(newHalfDay.precipitationProbability!!.thunderstorm),
rain = UnitUtils.validatePercent(newHalfDay.precipitationProbability!!.rain),
snow = UnitUtils.validatePercent(newHalfDay.precipitationProbability!!.snow),
ice = UnitUtils.validatePercent(newHalfDay.precipitationProbability!!.ice)
total = newHalfDay.precipitationProbability!!.total?.toValidRangeOrNull(),
thunderstorm = newHalfDay.precipitationProbability!!.thunderstorm?.toValidRangeOrNull(),
rain = newHalfDay.precipitationProbability!!.rain?.toValidRangeOrNull(),
snow = newHalfDay.precipitationProbability!!.snow?.toValidRangeOrNull(),
ice = newHalfDay.precipitationProbability!!.ice?.toValidRangeOrNull()
)
val maxPrecipitationProbability = if (initialPrecipitationProbability?.total == null) {
getHalfDayPrecipitationProbabilityFromHourlyList(halfDayHourlyList)
@ -963,11 +967,11 @@ private fun getHalfDayWeatherCodeFromHourlyList(
totPrecipitation: Precipitation?,
maxPrecipitationProbability: PrecipitationProbability?,
maxWind: Wind?,
avgCloudCover: Int?,
avgCloudCover: Ratio?,
avgVisibility: Distance?,
): WeatherCode? {
val minPrecipIntensity = 1.0.millimeters // in mm
val minPrecipProbability = 30.0 // in %
val minPrecipIntensity = 1.0.millimeters
val minPrecipProbability = 30.percent
val maxVisibilityHaze = 5000.meters
val maxVisibilityFog = 1000.meters
val maxWindSpeedWindy = 10.metersPerSecond
@ -975,7 +979,7 @@ private fun getHalfDayWeatherCodeFromHourlyList(
// If total precipitation is greater than 1 mm
// and max probability is greater than 30 % (assume 100 % if not reported)
if ((totPrecipitation?.total ?: 0.0.millimeters) > minPrecipIntensity &&
(maxPrecipitationProbability?.total ?: 100.0) > minPrecipProbability
(maxPrecipitationProbability?.total ?: 100.percent) > minPrecipProbability
) {
val isRain = maxPrecipitationProbability?.rain?.let { it > minPrecipProbability }
?: totPrecipitation!!.rain?.let { it.value > 0 }
@ -1047,8 +1051,8 @@ private fun getHalfDayWeatherCodeFromHourlyList(
// Its not raining, its not windy, and its not mysterious. Just cloudy
if (avgCloudCover != null) {
if (avgCloudCover > CLOUD_COVER_BKN) return WeatherCode.CLOUDY
if (avgCloudCover > CLOUD_COVER_FEW) return WeatherCode.PARTLY_CLOUDY
if (avgCloudCover > CLOUD_COVER_BKN.percent) return WeatherCode.CLOUDY
if (avgCloudCover > CLOUD_COVER_FEW.percent) return WeatherCode.PARTLY_CLOUDY
return WeatherCode.CLEAR
}
@ -1230,10 +1234,9 @@ private fun getHalfDayWindFromHourlyList(
private fun getHalfDayCloudCoverFromHourlyList(
halfDayHourlyList: List<Hourly>,
): Int? {
): Ratio? {
// average() would return NaN when called for an empty list
return halfDayHourlyList.mapNotNull { it.cloudCover }
.takeIf { it.isNotEmpty() }?.average()?.roundToInt()
return halfDayHourlyList.mapNotNull { it.cloudCover?.value }.takeIf { it.isNotEmpty() }?.average()?.permille
}
private fun getHalfDayAvgVisibilityFromHourlyList(
@ -1340,16 +1343,16 @@ private fun getSunshineDuration(
fun getDailyRelativeHumidity(
initialDailyRelativeHumidity: DailyRelativeHumidity?,
values: List<Double>?,
values: List<Ratio>?,
): DailyRelativeHumidity? {
if (values.isNullOrEmpty()) return initialDailyRelativeHumidity
return DailyRelativeHumidity(
average = UnitUtils.validatePercent(initialDailyRelativeHumidity?.average)
?: values.average(),
min = UnitUtils.validatePercent(initialDailyRelativeHumidity?.min)
average = initialDailyRelativeHumidity?.average?.toValidRangeOrNull()
?: values.map { it.value }.average().permille,
min = initialDailyRelativeHumidity?.min?.toValidRangeOrNull()
?: values.min(),
max = UnitUtils.validatePercent(initialDailyRelativeHumidity?.max)
max = initialDailyRelativeHumidity?.max?.toValidRangeOrNull()
?: values.max()
)
}
@ -1388,16 +1391,16 @@ fun getDailyPressure(
fun getDailyCloudCover(
initialDailyCloudCover: DailyCloudCover?,
values: List<Int>?,
values: List<Ratio>?,
): DailyCloudCover? {
if (values.isNullOrEmpty()) return initialDailyCloudCover
return DailyCloudCover(
average = UnitUtils.validatePercent(initialDailyCloudCover?.average)
?: values.average().roundToInt(),
min = UnitUtils.validatePercent(initialDailyCloudCover?.min)
average = initialDailyCloudCover?.average?.toValidRangeOrNull()
?: values.map { it.value }.average().permille,
min = initialDailyCloudCover?.min?.toValidRangeOrNull()
?: values.min(),
max = UnitUtils.validatePercent(initialDailyCloudCover?.max)
max = initialDailyCloudCover?.max?.toValidRangeOrNull()
?: values.max()
)
}
@ -1576,7 +1579,7 @@ internal fun completeCurrentFromHourlyData(
val initialFeelsLike = newCurrent.temperature?.feelsLike?.toValidOrNull()
val initialWind = newCurrent.wind?.validate()
val newWind = if (initialWind?.speed != null || hourly.wind?.speed == null) initialWind else hourly.wind
val newRelativeHumidity = UnitUtils.validatePercent(newCurrent.relativeHumidity)
val newRelativeHumidity = newCurrent.relativeHumidity?.toValidRangeOrNull()
?: hourly.relativeHumidity
val newDewPoint = newCurrent.dewPoint?.toValidOrNull()
?: if (newRelativeHumidity != null || initialTemp != null) {
@ -1623,7 +1626,7 @@ internal fun completeCurrentFromHourlyData(
relativeHumidity = newRelativeHumidity,
dewPoint = newDewPoint,
pressure = newCurrent.pressure?.toValidOrNull() ?: hourly.pressure,
cloudCover = UnitUtils.validatePercent(newCurrent.cloudCover) ?: hourly.cloudCover,
cloudCover = newCurrent.cloudCover?.toValidRangeOrNull() ?: hourly.cloudCover,
visibility = newCurrent.visibility?.toValidOrNull() ?: hourly.visibility,
ceiling = newCurrent.ceiling?.toValidOrNull()
)
@ -1634,7 +1637,7 @@ private fun completeCurrentTemperatureFromHourly(
initialFeelsLike: Temperature?,
hourlyTemperature: breezyweather.domain.weather.model.Temperature?,
windSpeed: Speed?,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
dewPoint: Temperature?,
): breezyweather.domain.weather.model.Temperature? {
if (initialTemp == null) return hourlyTemperature

View file

@ -61,6 +61,7 @@ import org.breezyweather.sources.brightsky.json.BrightSkyWeatherResult
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -215,10 +216,10 @@ class BrightSkyService @Inject constructor(
speed = result.windSpeed?.kilometersPerHour,
gusts = result.windGustSpeed?.kilometersPerHour
),
relativeHumidity = result.relativeHumidity?.toDouble(),
relativeHumidity = result.relativeHumidity?.percent,
dewPoint = result.dewPoint?.celsius,
pressure = result.pressure?.hectopascals,
cloudCover = result.cloudCover,
cloudCover = result.cloudCover?.percent,
visibility = result.visibility?.meters
)
}
@ -268,17 +269,17 @@ class BrightSkyService @Inject constructor(
total = result.precipitation?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = result.precipitationProbability?.toDouble()
total = result.precipitationProbability?.percent
),
wind = Wind(
degree = result.windDirection?.toDouble(),
speed = result.windSpeed?.kilometersPerHour,
gusts = result.windGustSpeed?.kilometersPerHour
),
relativeHumidity = result.relativeHumidity?.toDouble(),
relativeHumidity = result.relativeHumidity?.percent,
dewPoint = result.dewPoint?.celsius,
pressure = result.pressure?.hectopascals,
cloudCover = result.cloudCover,
cloudCover = result.cloudCover?.percent,
visibility = result.visibility?.toDouble()?.meters,
sunshineDuration = result.sunshine?.minutes
)

View file

@ -36,13 +36,13 @@ import org.breezyweather.common.source.WeatherSource
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import java.util.Calendar
import java.util.Date
import java.util.TimeZone
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
@ -146,7 +146,7 @@ class DebugService @Inject constructor() : WeatherSource {
dewPoint = Math.random().times(10).plus(5).celsius,
pressure = Math.random().times(100).plus(963).hectopascals,
visibility = Math.random().times(50000).meters,
cloudCover = Math.random().times(100).roundToInt()
cloudCover = Math.random().fraction
)
}

View file

@ -83,7 +83,7 @@ class GadgetbridgeService @Inject constructor() : BroadcastSource {
currentTemp = current?.temperature?.temperature?.inKelvins?.roundToInt(),
currentConditionCode = getWeatherCode(current?.weatherCode),
currentCondition = current?.weatherText,
currentHumidity = current?.relativeHumidity?.roundToInt(),
currentHumidity = current?.relativeHumidity?.inPercent?.roundToInt(),
windSpeed = current?.wind?.speed?.inKilometersPerHour?.toFloat(),
windDirection = current?.wind?.degree?.roundToInt(),
uvIndex = current?.uV?.index?.toFloat(),
@ -94,11 +94,11 @@ class GadgetbridgeService @Inject constructor() : BroadcastSource {
precipProbability = maxOfNullable(
today?.day?.precipitationProbability?.total,
today?.night?.precipitationProbability?.total
)?.roundToInt(),
)?.inPercent?.roundToInt(),
dewPoint = current?.dewPoint?.inKelvins?.roundToInt(),
pressure = current?.pressure?.inHectopascals?.toFloat(),
cloudCover = current?.cloudCover,
cloudCover = current?.cloudCover?.inPercent?.roundToInt(),
visibility = current?.visibility?.inMeters?.toFloat(),
sunRise = today?.sun?.riseDate?.time?.div(1000)?.toInt(),
@ -125,14 +125,14 @@ class GadgetbridgeService @Inject constructor() : BroadcastSource {
conditionCode = getWeatherCode(day.day?.weatherCode),
maxTemp = day.day?.temperature?.temperature?.inKelvins?.roundToInt(),
minTemp = day.night?.temperature?.temperature?.inKelvins?.roundToInt(),
humidity = day.relativeHumidity?.average?.roundToInt(),
humidity = day.relativeHumidity?.average?.inPercent?.roundToInt(),
windSpeed = maxWind?.speed?.inKilometersPerHour?.toFloat(),
windDirection = maxWind?.degree?.roundToInt(),
uvIndex = day.uV?.index?.toFloat(),
precipProbability = maxOfNullable(
day.day?.precipitationProbability?.total,
day.night?.precipitationProbability?.total
)?.roundToInt(),
)?.inPercent?.roundToInt(),
sunRise = day.sun?.riseDate?.time?.div(1000)?.toInt(),
sunSet = day.sun?.setDate?.time?.div(1000)?.toInt(),
@ -174,11 +174,11 @@ class GadgetbridgeService @Inject constructor() : BroadcastSource {
timestamp = hour.date.time.div(1000).toInt(),
temp = hour.temperature?.temperature?.inKelvins?.roundToInt(),
conditionCode = getWeatherCode(hour.weatherCode),
humidity = hour.relativeHumidity?.roundToInt(),
humidity = hour.relativeHumidity?.inPercent?.roundToInt(),
windSpeed = hour.wind?.speed?.inKilometersPerHour?.toFloat(),
windDirection = hour.wind?.degree?.roundToInt(),
uvIndex = hour.uV?.index?.toFloat(),
precipProbability = hour.precipitationProbability?.total?.roundToInt()
precipProbability = hour.precipitationProbability?.total?.inPercent?.roundToInt()
)
}
}

View file

@ -96,6 +96,7 @@ import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgr
import org.breezyweather.unit.precipitation.Precipitation.Companion.centimeters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.HttpException
@ -395,10 +396,10 @@ class OpenMeteoService @Inject constructor(
gusts = current.windGusts?.metersPerSecond
),
uV = UV(index = current.uvIndex),
relativeHumidity = current.relativeHumidity?.toDouble(),
relativeHumidity = current.relativeHumidity?.percent,
dewPoint = current.dewPoint?.celsius,
pressure = current.pressureMsl?.hectopascals,
cloudCover = current.cloudCover,
cloudCover = current.cloudCover?.percent,
visibility = current.visibility?.meters
)
}
@ -435,9 +436,9 @@ class OpenMeteoService @Inject constructor(
uV = UV(index = dailyResult.uvIndexMax?.getOrNull(i)),
sunshineDuration = dailyResult.sunshineDuration?.getOrNull(i)?.seconds,
relativeHumidity = DailyRelativeHumidity(
average = dailyResult.relativeHumidityMean?.getOrNull(i)?.toDouble(),
max = dailyResult.relativeHumidityMax?.getOrNull(i)?.toDouble(),
min = dailyResult.relativeHumidityMin?.getOrNull(i)?.toDouble()
average = dailyResult.relativeHumidityMean?.getOrNull(i)?.percent,
max = dailyResult.relativeHumidityMax?.getOrNull(i)?.percent,
min = dailyResult.relativeHumidityMin?.getOrNull(i)?.percent
),
dewPoint = DailyDewPoint(
average = dailyResult.dewPointMean?.getOrNull(i)?.celsius,
@ -450,9 +451,9 @@ class OpenMeteoService @Inject constructor(
min = dailyResult.pressureMslMin?.getOrNull(i)?.hectopascals
),
cloudCover = DailyCloudCover(
average = dailyResult.cloudCoverMean?.getOrNull(i),
max = dailyResult.cloudCoverMax?.getOrNull(i),
min = dailyResult.cloudCoverMin?.getOrNull(i)
average = dailyResult.cloudCoverMean?.getOrNull(i)?.percent,
max = dailyResult.cloudCoverMax?.getOrNull(i)?.percent,
min = dailyResult.cloudCoverMin?.getOrNull(i)?.percent
),
visibility = DailyVisibility(
average = dailyResult.visibilityMean?.getOrNull(i)?.meters,
@ -489,7 +490,7 @@ class OpenMeteoService @Inject constructor(
snow = hourlyResult.snowfall?.getOrNull(i)?.centimeters
),
precipitationProbability = PrecipitationProbability(
total = hourlyResult.precipitationProbability?.getOrNull(i)?.toDouble()
total = hourlyResult.precipitationProbability?.getOrNull(i)?.percent
),
wind = Wind(
degree = hourlyResult.windDirection?.getOrNull(i)?.toDouble(),
@ -497,10 +498,10 @@ class OpenMeteoService @Inject constructor(
gusts = hourlyResult.windGusts?.getOrNull(i)?.metersPerSecond
),
uV = UV(index = hourlyResult.uvIndex?.getOrNull(i)),
relativeHumidity = hourlyResult.relativeHumidity?.getOrNull(i)?.toDouble(),
relativeHumidity = hourlyResult.relativeHumidity?.getOrNull(i)?.percent,
dewPoint = hourlyResult.dewPoint?.getOrNull(i)?.celsius,
pressure = hourlyResult.pressureMsl?.getOrNull(i)?.hectopascals,
cloudCover = hourlyResult.cloudCover?.getOrNull(i),
cloudCover = hourlyResult.cloudCover?.getOrNull(i)?.percent,
visibility = hourlyResult.visibility?.getOrNull(i)?.meters
)
)

View file

@ -63,6 +63,7 @@ import org.breezyweather.unit.distance.Distance.Companion.kilometers
import org.breezyweather.unit.precipitation.Precipitation.Companion.centimeters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -170,10 +171,10 @@ class PirateWeatherService @Inject constructor(
gusts = result.windGust?.metersPerSecond
),
uV = UV(index = result.uvIndex),
relativeHumidity = result.humidity?.times(100),
relativeHumidity = result.humidity?.fraction,
dewPoint = result.dewPoint?.celsius,
pressure = result.pressure?.hectopascals,
cloudCover = result.cloudCover?.times(100)?.roundToInt(),
cloudCover = result.cloudCover?.fraction,
visibility = result.visibility?.kilometers,
dailyForecast = dailySummary,
hourlyForecast = hourlySummary
@ -205,10 +206,10 @@ class PirateWeatherService @Inject constructor(
)
),
uV = UV(index = result.uvIndex),
relativeHumidity = DailyRelativeHumidity(average = result.humidity?.times(100)),
relativeHumidity = DailyRelativeHumidity(average = result.humidity?.fraction),
dewPoint = DailyDewPoint(average = result.dewPoint?.celsius),
pressure = DailyPressure(average = result.pressure?.hectopascals),
cloudCover = DailyCloudCover(average = result.cloudCover?.times(100)?.roundToInt()),
cloudCover = DailyCloudCover(average = result.cloudCover?.fraction),
visibility = DailyVisibility(average = result.visibility?.kilometers)
)
}
@ -237,7 +238,7 @@ class PirateWeatherService @Inject constructor(
ice = result.iceAccumulation?.centimeters
),
precipitationProbability = PrecipitationProbability(
total = result.precipProbability?.times(100)
total = result.precipProbability?.fraction
),
wind = Wind(
degree = result.windBearing,
@ -247,10 +248,10 @@ class PirateWeatherService @Inject constructor(
uV = UV(
index = result.uvIndex
),
relativeHumidity = result.humidity?.times(100),
relativeHumidity = result.humidity?.fraction,
dewPoint = result.dewPoint?.celsius,
pressure = result.pressure?.hectopascals,
cloudCover = result.cloudCover?.times(100)?.roundToInt(),
cloudCover = result.cloudCover?.fraction,
visibility = result.visibility?.kilometers
)
}

View file

@ -54,6 +54,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -441,6 +442,7 @@ private fun AirQualityChart(
markerVisibilityListener: CartesianMarkerVisibilityListener,
) {
val context = LocalContext.current
val resources = LocalResources.current
val maxY = remember(mappedValues, selectedPollutant) {
max(
@ -475,38 +477,46 @@ private fun AirQualityChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY.toDouble(),
{ _, value, _ ->
if (selectedPollutant == null) {
UnitUtils.formatInt(context, value.roundToInt())
} else {
PollutantIndex.getUnit(selectedPollutant).formatMeasure(context, value)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY.toDouble(),
endAxisValueFormatter = remember(selectedPollutant) {
{ _, value, _ ->
if (selectedPollutant == null) {
UnitUtils.formatInt(context, value.roundToInt())
} else {
PollutantIndex.getUnit(selectedPollutant).formatMeasure(context, value)
}
}
},
persistentListOf(
((selectedPollutant?.thresholds ?: PollutantIndex.aqiThresholds).reversed().map { it.toFloat() }).zip(
context.resources.getIntArray(PollutantIndex.colorsArrayId).reversed().map { Color(it) }
).toMap().toImmutableMap()
),
trendHorizontalLines = persistentMapOf(
(selectedPollutant?.let { it.thresholds[3] } ?: PollutantIndex.aqiThresholds[3]).toDouble() to
context.resources.getStringArray(R.array.air_quality_levels)[3]
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }
?.let {
UnitUtils.formatInt(
context,
if (selectedPollutant == null) {
it.getIndex()!!
} else {
it.getConcentration(selectedPollutant)!!.roundToInt()
}
)
} ?: "-"
colors = remember(selectedPollutant) {
persistentListOf(
((selectedPollutant?.thresholds ?: PollutantIndex.aqiThresholds).reversed().map { it.toFloat() }).zip(
resources.getIntArray(PollutantIndex.colorsArrayId).reversed().map { Color(it) }
).toMap().toImmutableMap()
)
},
trendHorizontalLines = remember(selectedPollutant) {
persistentMapOf(
(selectedPollutant?.let { it.thresholds[3] } ?: PollutantIndex.aqiThresholds[3]).toDouble() to
resources.getStringArray(R.array.air_quality_levels)[3]
)
},
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }
?.let {
UnitUtils.formatInt(
context,
if (selectedPollutant == null) {
it.getIndex()!!
} else {
it.getConcentration(selectedPollutant)!!.roundToInt()
}
)
} ?: "-"
}
},
endAxisItemPlacer = remember(selectedPollutant) {
SpecificVerticalAxisItemPlacer(

View file

@ -50,7 +50,6 @@ import breezyweather.domain.weather.model.Daily
import breezyweather.domain.weather.model.Hourly
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener
@ -60,14 +59,19 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import org.breezyweather.R
import org.breezyweather.common.extensions.cloudCoverScaleThresholds
import org.breezyweather.common.extensions.CLOUD_COVER_BKN
import org.breezyweather.common.extensions.CLOUD_COVER_FEW
import org.breezyweather.common.extensions.CLOUD_COVER_OVC
import org.breezyweather.common.extensions.CLOUD_COVER_SCT
import org.breezyweather.common.extensions.CLOUD_COVER_SKC
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.formatTime
import org.breezyweather.common.extensions.formatValue
import org.breezyweather.common.extensions.getCloudCoverDescription
import org.breezyweather.common.extensions.getFormattedTime
import org.breezyweather.common.extensions.is12Hour
import org.breezyweather.common.extensions.toDate
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.domain.weather.model.getFullLabel
import org.breezyweather.domain.weather.model.getRangeDescriptionSummary
import org.breezyweather.domain.weather.model.getRangeSummary
@ -75,8 +79,9 @@ import org.breezyweather.ui.common.charts.BreezyLineChart
import org.breezyweather.ui.common.widgets.Material3ExpressiveCardListItem
import org.breezyweather.ui.settings.preference.bottomInsetItem
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import java.util.Date
import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.DurationUnit
@ -85,7 +90,7 @@ fun DetailsCloudCover(
location: Location,
hourlyList: ImmutableList<Hourly>,
daily: Daily,
defaultValue: Pair<Date, Int>?,
defaultValue: Pair<Date, Ratio>?,
modifier: Modifier = Modifier,
) {
val mappedValues = remember(hourlyList) {
@ -94,7 +99,7 @@ fun DetailsCloudCover(
.associate { it.date.time to it.cloudCover!! }
.toImmutableMap()
}
var activeItem: Pair<Date, Int>? by remember { mutableStateOf(null) }
var activeItem: Pair<Date, Ratio>? by remember { mutableStateOf(null) }
val markerVisibilityListener = remember {
object : CartesianMarkerVisibilityListener {
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
@ -181,8 +186,8 @@ fun DetailsCloudCover(
private fun CloudCoverHeader(
location: Location,
daily: Daily,
activeItem: Pair<Date, Int>?,
defaultValue: Pair<Date, Int>?,
activeItem: Pair<Date, Ratio>?,
defaultValue: Pair<Date, Ratio>?,
) {
val context = LocalContext.current
@ -204,7 +209,7 @@ private fun CloudCoverHeader(
@Composable
private fun CloudCoverItem(
header: String?,
cloudCover: Int?,
cloudCover: Ratio?,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@ -217,13 +222,11 @@ private fun CloudCoverItem(
style = MaterialTheme.typography.labelMedium
)
TextFixedHeight(
text = cloudCover?.let {
UnitUtils.formatPercent(context, it.toDouble())
} ?: "",
text = cloudCover?.formatPercent(context) ?: "",
style = MaterialTheme.typography.displaySmall
)
TextFixedHeight(
text = getCloudCoverDescription(context, cloudCover) ?: "",
text = cloudCover?.getCloudCoverDescription(context) ?: "",
style = MaterialTheme.typography.labelMedium
)
}
@ -257,7 +260,7 @@ private fun CloudCoverSummary(
@Composable
private fun CloudCoverChart(
location: Location,
mappedValues: ImmutableMap<Long, Int>,
mappedValues: ImmutableMap<Long, Ratio>,
daily: Daily,
markerVisibilityListener: CartesianMarkerVisibilityListener,
) {
@ -269,35 +272,31 @@ private fun CloudCoverChart(
lineSeries {
series(
x = mappedValues.keys,
y = mappedValues.values
y = mappedValues.values.map { it.inPercent }
)
}
}
}
BreezyLineChart(
location,
modelProducer,
daily.date,
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = 100.0,
endAxisValueFormatter = remember {
CartesianValueFormatter { _, value, _ ->
UnitUtils.formatPercent(context, value)
}
},
persistentListOf(
persistentMapOf(
100f to Color(213, 213, 205),
98f to Color(198, 201, 201),
95f to Color(171, 180, 179),
50f to Color(116, 116, 116),
10f to Color(132, 119, 70),
0f to Color(146, 130, 70)
endAxisValueFormatter = remember { { _, value, _ -> value.percent.formatPercent(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
100f to Color(213, 213, 205),
98f to Color(198, 201, 201),
95f to Color(171, 180, 179),
50f to Color(116, 116, 116),
10f to Color(132, 119, 70),
0f to Color(146, 130, 70)
)
)
),
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ 20.0 }) // Every 20 %
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ 20.0 }) }, // Every 20 %
markerVisibilityListener = markerVisibilityListener
)
}
@ -333,6 +332,14 @@ fun CloudCoverScale(
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val cloudCoverScaleThresholds = listOf(
0.0.percent,
CLOUD_COVER_SKC.percent,
CLOUD_COVER_FEW.percent,
CLOUD_COVER_SCT.percent,
CLOUD_COVER_BKN.percent,
CLOUD_COVER_OVC.percent
)
Material3ExpressiveCardListItem(
modifier = modifier,
@ -363,11 +370,9 @@ fun CloudCoverScale(
)
}
cloudCoverScaleThresholds.dropLast(1).forEachIndexed { index, startingValue ->
val startingValueFormatted = UnitUtils.formatNumber(context, startingValue, precision = 1)
val startingValueFormatted = startingValue.formatValue(context)
val endingValueFormatted = cloudCoverScaleThresholds.getOrElse(index + 1) { null }
?.let {
" ${UnitUtils.formatNumber(context, it, precision = 1)}"
}
?.let { " ${it.formatValue(context)}" }
?: "+"
Row(
verticalAlignment = Alignment.CenterVertically,
@ -377,7 +382,7 @@ fun CloudCoverScale(
.padding(top = dimensionResource(R.dimen.small_margin))
) {
Text(
text = getCloudCoverDescription(context, (startingValue + 0.1).roundToInt())!!,
text = startingValue.getCloudCoverDescription(context)!!,
modifier = Modifier.weight(1.5f)
)
Text(

View file

@ -104,6 +104,7 @@ import org.breezyweather.ui.settings.preference.bottomInsetItem
import org.breezyweather.ui.theme.resource.ResourceHelper
import org.breezyweather.ui.theme.resource.ResourcesProviderFactory
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.temperature.Temperature
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import org.breezyweather.unit.temperature.Temperature.Companion.deciCelsius
@ -163,7 +164,7 @@ fun DetailsConditions(
.associate { it.date.time to it.precipitationProbability!!.total!! }
.toImmutableMap()
}
var activeProbabilityItem: Pair<Date, Double>? by remember { mutableStateOf(null) }
var activeProbabilityItem: Pair<Date, Ratio>? by remember { mutableStateOf(null) }
val probabilityMarkerVisibilityListener = remember {
object : CartesianMarkerVisibilityListener {
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
@ -801,67 +802,73 @@ private fun TemperatureChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ ->
value.toTemperature(temperatureUnit)
.formatMeasure(context, valueWidth = UnitWidth.NARROW, unitWidth = UnitWidth.NARROW)
},
persistentListOf(
persistentMapOf(
47.celsius.toDouble(temperatureUnit).toFloat() to Color(71, 14, 0),
30.celsius.toDouble(temperatureUnit).toFloat() to Color(232, 83, 25),
21.celsius.toDouble(temperatureUnit).toFloat() to Color(243, 183, 4),
10.celsius.toDouble(temperatureUnit).toFloat() to Color(128, 147, 24),
1.celsius.toDouble(temperatureUnit).toFloat() to Color(68, 125, 99),
0.celsius.toDouble(temperatureUnit).toFloat() to Color(93, 133, 198),
-4.celsius.toDouble(temperatureUnit).toFloat() to Color(100, 166, 189),
-8.celsius.toDouble(temperatureUnit).toFloat() to Color(106, 191, 181),
-15.celsius.toDouble(temperatureUnit).toFloat() to Color(157, 219, 217),
-25.celsius.toDouble(temperatureUnit).toFloat() to Color(143, 89, 169),
-40.celsius.toDouble(temperatureUnit).toFloat() to Color(162, 70, 145),
-55.celsius.toDouble(temperatureUnit).toFloat() to Color(202, 172, 195),
-70.celsius.toDouble(temperatureUnit).toFloat() to Color(115, 70, 105)
),
persistentMapOf(
50f to Color(128, 128, 128, 160),
0f to Color(128, 128, 128, 160)
)
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.let { hourly ->
hourly.weatherCode?.let {
val ss = SpannableString("abc")
val d = ResourceHelper.getWeatherIcon(provider, it, hourly.isDaylight)
d.setBounds(0, 0, 64, 64)
val span = ImageSpan(d, ImageSpan.ALIGN_BASELINE)
ss.setSpan(span, 0, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
ss
}
} ?: "-"
},
trendHorizontalLines = buildMap {
normals?.let {
it.daytimeTemperature?.let { normal ->
put(
normal.toDouble(temperatureUnit),
context.getString(R.string.temperature_normal_short)
)
}
it.nighttimeTemperature?.let { normal ->
put(
normal.toDouble(temperatureUnit),
context.getString(R.string.temperature_normal_short)
)
}
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember {
{ _, value, _ ->
value.toTemperature(temperatureUnit)
.formatMeasure(context, valueWidth = UnitWidth.NARROW, unitWidth = UnitWidth.NARROW)
}
}.toImmutableMap(),
minY = minY,
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ step })
},
colors = remember {
persistentListOf(
persistentMapOf(
47.celsius.toDouble(temperatureUnit).toFloat() to Color(71, 14, 0),
30.celsius.toDouble(temperatureUnit).toFloat() to Color(232, 83, 25),
21.celsius.toDouble(temperatureUnit).toFloat() to Color(243, 183, 4),
10.celsius.toDouble(temperatureUnit).toFloat() to Color(128, 147, 24),
1.celsius.toDouble(temperatureUnit).toFloat() to Color(68, 125, 99),
0.celsius.toDouble(temperatureUnit).toFloat() to Color(93, 133, 198),
-4.celsius.toDouble(temperatureUnit).toFloat() to Color(100, 166, 189),
-8.celsius.toDouble(temperatureUnit).toFloat() to Color(106, 191, 181),
-15.celsius.toDouble(temperatureUnit).toFloat() to Color(157, 219, 217),
-25.celsius.toDouble(temperatureUnit).toFloat() to Color(143, 89, 169),
-40.celsius.toDouble(temperatureUnit).toFloat() to Color(162, 70, 145),
-55.celsius.toDouble(temperatureUnit).toFloat() to Color(202, 172, 195),
-70.celsius.toDouble(temperatureUnit).toFloat() to Color(115, 70, 105)
),
persistentMapOf(
50f to Color(128, 128, 128, 160),
0f to Color(128, 128, 128, 160)
)
)
},
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.let { hourly ->
hourly.weatherCode?.let {
val ss = SpannableString("abc")
val d = ResourceHelper.getWeatherIcon(provider, it, hourly.isDaylight)
d.setBounds(0, 0, 64, 64)
val span = ImageSpan(d, ImageSpan.ALIGN_BASELINE)
ss.setSpan(span, 0, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
ss
}
} ?: "-"
}
},
trendHorizontalLines = remember {
buildMap {
normals?.let {
it.daytimeTemperature?.let { normal ->
put(
normal.toDouble(temperatureUnit),
context.getString(R.string.temperature_normal_short)
)
}
it.nighttimeTemperature?.let { normal ->
put(
normal.toDouble(temperatureUnit),
context.getString(R.string.temperature_normal_short)
)
}
}
}.toImmutableMap()
},
minY = minY,
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ step }) },
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -43,7 +43,6 @@ import breezyweather.domain.weather.model.Daily
import breezyweather.domain.weather.model.Hourly
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarkerVisibilityListener
@ -54,13 +53,13 @@ import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import org.breezyweather.R
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getFormattedTime
import org.breezyweather.common.extensions.is12Hour
import org.breezyweather.common.extensions.roundDownToNearestMultiplier
import org.breezyweather.common.extensions.roundUpToNearestMultiplier
import org.breezyweather.common.extensions.toDate
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.domain.settings.SettingsManager
import org.breezyweather.domain.weather.model.getFullLabel
import org.breezyweather.domain.weather.model.getRangeContentDescriptionSummary
@ -68,6 +67,8 @@ import org.breezyweather.domain.weather.model.getRangeSummary
import org.breezyweather.ui.common.charts.BreezyLineChart
import org.breezyweather.ui.settings.preference.bottomInsetItem
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.temperature.Temperature
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import org.breezyweather.unit.temperature.toTemperature
@ -78,7 +79,7 @@ fun DetailsHumidity(
location: Location,
hourlyList: ImmutableList<Hourly>,
daily: Daily,
defaultHumidityValue: Pair<Date, Double>?,
defaultHumidityValue: Pair<Date, Ratio>?,
defaultDewPointValue: Pair<Date, Temperature>?,
modifier: Modifier = Modifier,
) {
@ -89,7 +90,7 @@ fun DetailsHumidity(
.associate { it.date.time to it.relativeHumidity!! }
.toImmutableMap()
}
var activeHumidityItem: Pair<Date, Double>? by remember { mutableStateOf(null) }
var activeHumidityItem: Pair<Date, Ratio>? by remember { mutableStateOf(null) }
val humidityMarkerVisibilityListener = remember {
object : CartesianMarkerVisibilityListener {
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
@ -204,7 +205,10 @@ fun DetailsHumidity(
}
item {
DetailsCardText(
stringResource(R.string.dew_point_about_description, UnitUtils.formatPercent(context, 100.0))
stringResource(
R.string.dew_point_about_description,
100.percent.formatPercent(context, UnitWidth.NARROW)
)
)
}
bottomInsetItem()
@ -215,8 +219,8 @@ fun DetailsHumidity(
fun HumidityHeader(
location: Location,
daily: Daily,
activeItem: Pair<Date, Double>?,
defaultValue: Pair<Date, Double>?,
activeItem: Pair<Date, Ratio>?,
defaultValue: Pair<Date, Ratio>?,
) {
val context = LocalContext.current
@ -270,7 +274,7 @@ private fun HumiditySummary(
@Composable
private fun HumidityItem(
header: @Composable () -> Unit,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@ -280,9 +284,7 @@ private fun HumidityItem(
) {
header()
TextFixedHeight(
text = relativeHumidity?.let {
UnitUtils.formatPercent(context, it)
} ?: "",
text = relativeHumidity?.formatPercent(context) ?: "",
style = MaterialTheme.typography.displaySmall
)
}
@ -291,7 +293,7 @@ private fun HumidityItem(
@Composable
private fun HumidityChart(
location: Location,
mappedValues: ImmutableMap<Long, Double>,
mappedValues: ImmutableMap<Long, Ratio>,
daily: Daily,
markerVisibilityListener: CartesianMarkerVisibilityListener,
) {
@ -299,10 +301,6 @@ private fun HumidityChart(
val maxY = 100.0
val endAxisValueFormatter = CartesianValueFormatter { _, value, _ ->
UnitUtils.formatPercent(context, value)
}
val modelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(location) {
@ -310,44 +308,44 @@ private fun HumidityChart(
lineSeries {
series(
x = mappedValues.keys,
y = mappedValues.values
y = mappedValues.values.map { it.inPercent }
)
}
}
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
endAxisValueFormatter,
persistentListOf(
persistentMapOf(
100f to Color(56, 70, 114),
97f to Color(56, 98, 157),
93f to Color(56, 123, 173),
90f to Color(56, 132, 173),
87f to Color(56, 135, 173),
83f to Color(56, 148, 173),
80f to Color(56, 157, 173),
75f to Color(56, 160, 173),
70f to Color(56, 174, 173),
60f to Color(56, 173, 121),
50f to Color(105, 173, 56),
40f to Color(173, 146, 56),
30f to Color(173, 110, 56),
0f to Color(173, 85, 56)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember { { _, value, _ -> value.percent.formatPercent(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
100f to Color(56, 70, 114),
97f to Color(56, 98, 157),
93f to Color(56, 123, 173),
90f to Color(56, 132, 173),
87f to Color(56, 135, 173),
83f to Color(56, 148, 173),
80f to Color(56, 157, 173),
75f to Color(56, 160, 173),
70f to Color(56, 174, 173),
60f to Color(56, 173, 121),
50f to Color(105, 173, 56),
40f to Color(173, 146, 56),
30f to Color(173, 110, 56),
0f to Color(173, 85, 56)
)
)
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.let {
UnitUtils.formatPercent(context, it)
} ?: "-"
},
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ 20.0 }) // Every 20 %
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.formatPercent(context, UnitWidth.NARROW) ?: "-"
}
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ 20.0 }) }, // Every 20 %
markerVisibilityListener = markerVisibilityListener
)
}
@ -471,43 +469,47 @@ private fun DewPointChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ ->
value.toTemperature(temperatureUnit)
.formatMeasure(context, valueWidth = UnitWidth.NARROW, unitWidth = UnitWidth.NARROW)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember {
{ _, value, _ ->
value.toTemperature(temperatureUnit)
.formatMeasure(context, valueWidth = UnitWidth.NARROW, unitWidth = UnitWidth.NARROW)
}
},
persistentListOf(
persistentMapOf(
// TODO: Duplicate of temperature colors
47.celsius.toDouble(temperatureUnit).toFloat() to Color(71, 14, 0),
30.celsius.toDouble(temperatureUnit).toFloat() to Color(232, 83, 25),
21.celsius.toDouble(temperatureUnit).toFloat() to Color(243, 183, 4),
10.celsius.toDouble(temperatureUnit).toFloat() to Color(128, 147, 24),
1.celsius.toDouble(temperatureUnit).toFloat() to Color(68, 125, 99),
0.celsius.toDouble(temperatureUnit).toFloat() to Color(93, 133, 198),
-4.celsius.toDouble(temperatureUnit).toFloat() to Color(100, 166, 189),
-8.celsius.toDouble(temperatureUnit).toFloat() to Color(106, 191, 181),
-15.celsius.toDouble(temperatureUnit).toFloat() to Color(157, 219, 217),
-25.celsius.toDouble(temperatureUnit).toFloat() to Color(143, 89, 169),
-40.celsius.toDouble(temperatureUnit).toFloat() to Color(162, 70, 145),
-55.celsius.toDouble(temperatureUnit).toFloat() to Color(202, 172, 195),
-70.celsius.toDouble(temperatureUnit).toFloat() to Color(115, 70, 105)
colors = remember {
persistentListOf(
persistentMapOf(
// TODO: Duplicate of temperature colors
47.celsius.toDouble(temperatureUnit).toFloat() to Color(71, 14, 0),
30.celsius.toDouble(temperatureUnit).toFloat() to Color(232, 83, 25),
21.celsius.toDouble(temperatureUnit).toFloat() to Color(243, 183, 4),
10.celsius.toDouble(temperatureUnit).toFloat() to Color(128, 147, 24),
1.celsius.toDouble(temperatureUnit).toFloat() to Color(68, 125, 99),
0.celsius.toDouble(temperatureUnit).toFloat() to Color(93, 133, 198),
-4.celsius.toDouble(temperatureUnit).toFloat() to Color(100, 166, 189),
-8.celsius.toDouble(temperatureUnit).toFloat() to Color(106, 191, 181),
-15.celsius.toDouble(temperatureUnit).toFloat() to Color(157, 219, 217),
-25.celsius.toDouble(temperatureUnit).toFloat() to Color(143, 89, 169),
-40.celsius.toDouble(temperatureUnit).toFloat() to Color(162, 70, 145),
-55.celsius.toDouble(temperatureUnit).toFloat() to Color(202, 172, 195),
-70.celsius.toDouble(temperatureUnit).toFloat() to Color(115, 70, 105)
)
)
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.formatMeasure(
context,
valueWidth = UnitWidth.NARROW,
unitWidth = UnitWidth.NARROW
) ?: "-"
},
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.formatMeasure(
context,
valueWidth = UnitWidth.NARROW,
unitWidth = UnitWidth.NARROW
) ?: "-"
}
},
minY = minY,
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ step })
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ step }) },
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -53,7 +53,6 @@ import breezyweather.domain.weather.model.PrecipitationDuration
import breezyweather.domain.weather.model.PrecipitationProbability
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
import com.patrykandpatrick.vico.core.cartesian.data.CartesianValueFormatter
import com.patrykandpatrick.vico.core.cartesian.data.columnSeries
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
import com.patrykandpatrick.vico.core.cartesian.marker.CartesianMarker
@ -66,6 +65,7 @@ import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import org.breezyweather.R
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.formatTime
import org.breezyweather.common.extensions.getFormattedTime
import org.breezyweather.common.extensions.is12Hour
@ -81,6 +81,8 @@ import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.precipitation.Precipitation
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.precipitation.toPrecipitation
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import java.util.Date
import kotlin.math.max
import kotlin.time.Duration
@ -128,7 +130,7 @@ fun DetailsPrecipitation(
.associate { it.date.time to it.precipitationProbability!!.total!! }
.toImmutableMap()
}
var activeProbabilityItem: Pair<Date, Double>? by remember { mutableStateOf(null) }
var activeProbabilityItem: Pair<Date, Ratio>? by remember { mutableStateOf(null) }
val probabilityMarkerVisibilityListener = remember {
object : CartesianMarkerVisibilityListener {
override fun onShown(marker: CartesianMarker, targets: List<CartesianMarker.Target>) {
@ -366,47 +368,51 @@ internal fun PrecipitationChart(
}
BreezyBarChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ -> value.toPrecipitation(precipitationUnit).formatMeasure(context, precipitationUnit) },
Fill(Color(60, 116, 160).toArgb()),
/*persistentMapOf(
50 to Fill(Color(168, 168, 168).toArgb()),
31 to Fill(Color(161, 59, 161).toArgb()),
20 to Fill(Color(161, 59, 59).toArgb()),
15 to Fill(Color(161, 161, 59).toArgb()),
10 to Fill(Color(130, 161, 59).toArgb()),
8 to Fill(Color(59, 161, 61).toArgb()),
6 to Fill(Color(59, 161, 161).toArgb()),
0.6 to Fill(Color(60, 116, 160).toArgb()),
0.0 to Fill(Color(111, 111, 111).toArgb())
)*/
trendHorizontalLines = buildMap {
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_HEAVY
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_heavy)
)
if (maxY < breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_HEAVY.times(2.0)
.millimeters.toDouble(precipitationUnit)
) {
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_MEDIUM
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_medium)
)
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_LIGHT
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_light)
)
}
}.toImmutableMap(),
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ step })
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember {
{ _, value, _ -> value.toPrecipitation(precipitationUnit).formatMeasure(context, precipitationUnit) }
},
barColorFill = remember { Fill(Color(60, 116, 160).toArgb()) },
/*colors = remember {
persistentMapOf(
50 to Fill(Color(168, 168, 168).toArgb()),
31 to Fill(Color(161, 59, 161).toArgb()),
20 to Fill(Color(161, 59, 59).toArgb()),
15 to Fill(Color(161, 161, 59).toArgb()),
10 to Fill(Color(130, 161, 59).toArgb()),
8 to Fill(Color(59, 161, 61).toArgb()),
6 to Fill(Color(59, 161, 161).toArgb()),
0.6 to Fill(Color(60, 116, 160).toArgb()),
0.0 to Fill(Color(111, 111, 111).toArgb())
)
}*/
trendHorizontalLines = remember {
buildMap {
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_HEAVY
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_heavy)
)
if (maxY < breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_HEAVY.times(2.0)
.millimeters.toDouble(precipitationUnit)
) {
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_MEDIUM
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_medium)
)
put(
breezyweather.domain.weather.model.Precipitation.PRECIPITATION_HOURLY_LIGHT
.millimeters.toDouble(precipitationUnit),
context.getString(R.string.precipitation_intensity_light)
)
}
}.toImmutableMap()
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ step }) },
markerVisibilityListener = markerVisibilityListener
)
}
@ -503,7 +509,7 @@ fun DailyPrecipitationDetail(
fun PrecipitationProbabilityHeader(
location: Location,
daily: Daily,
activeItem: Pair<Date, Double>?,
activeItem: Pair<Date, Ratio>?,
) {
val context = LocalContext.current
@ -523,7 +529,7 @@ fun PrecipitationProbabilityHeader(
@Composable
private fun PrecipitationProbabilityItem(
header: @Composable () -> Unit,
precipitationProbability: Double?,
precipitationProbability: Ratio?,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
@ -536,11 +542,11 @@ private fun PrecipitationProbabilityItem(
} else {
TextFixedHeight(
text = "",
style = MaterialTheme.typography.displaySmall
style = MaterialTheme.typography.labelMedium
)
}
TextFixedHeight(
text = precipitationProbability?.let { pp -> UnitUtils.formatPercent(context, pp) } ?: "",
text = precipitationProbability?.formatPercent(context) ?: "",
style = MaterialTheme.typography.displaySmall
)
}
@ -584,7 +590,7 @@ private fun PrecipitationProbabilitySummary(
@Composable
internal fun PrecipitationProbabilityChart(
location: Location,
mappedValues: ImmutableMap<Long, Double>,
mappedValues: ImmutableMap<Long, Ratio>,
daily: Daily,
markerVisibilityListener: CartesianMarkerVisibilityListener,
) {
@ -592,10 +598,6 @@ internal fun PrecipitationProbabilityChart(
val maxY = 100.0
val endAxisValueFormatter = CartesianValueFormatter { _, value, _ ->
UnitUtils.formatPercent(context, value)
}
val modelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(location) {
@ -603,28 +605,28 @@ internal fun PrecipitationProbabilityChart(
lineSeries {
series(
x = mappedValues.keys,
y = mappedValues.values
y = mappedValues.values.map { it.inPercent }
)
}
}
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
endAxisValueFormatter,
persistentListOf(
persistentMapOf(
// TODO
100f to Color(60, 116, 160),
0f to Color(60, 116, 160)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember { { _, value, _ -> value.percent.formatPercent(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
// TODO
100f to Color(60, 116, 160),
0f to Color(60, 116, 160)
)
)
),
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ 20.0 }) // Every 20 %
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ 20.0 }) }, // Every 20 %
markerVisibilityListener = markerVisibilityListener
)
}
@ -634,22 +636,22 @@ internal fun PrecipitationProbabilityDetails(
daytimePrecProb: PrecipitationProbability?,
nighttimePrecProb: PrecipitationProbability?,
) {
val daytimePrecProbItems = mutableListOf<Pair<Int, Double?>>()
val nighttimePrecProbItems = mutableListOf<Pair<Int, Double?>>()
val daytimePrecProbItems = mutableListOf<Pair<Int, Ratio?>>()
val nighttimePrecProbItems = mutableListOf<Pair<Int, Ratio?>>()
if ((daytimePrecProb?.rain ?: 0.0) > 0 || (nighttimePrecProb?.rain ?: 0.0) > 0) {
if ((daytimePrecProb?.rain?.value ?: 0) > 0 || (nighttimePrecProb?.rain?.value ?: 0) > 0) {
daytimePrecProbItems.add(Pair(R.string.precipitation_rain, daytimePrecProb?.rain))
nighttimePrecProbItems.add(Pair(R.string.precipitation_rain, nighttimePrecProb?.rain))
}
if ((daytimePrecProb?.snow ?: 0.0) > 0 || (nighttimePrecProb?.snow ?: 0.0) > 0) {
if ((daytimePrecProb?.snow?.value ?: 0) > 0 || (nighttimePrecProb?.snow?.value ?: 0) > 0) {
daytimePrecProbItems.add(Pair(R.string.precipitation_snow, daytimePrecProb?.snow))
nighttimePrecProbItems.add(Pair(R.string.precipitation_snow, nighttimePrecProb?.snow))
}
if ((daytimePrecProb?.ice ?: 0.0) > 0 || (nighttimePrecProb?.ice ?: 0.0) > 0) {
if ((daytimePrecProb?.ice?.value ?: 0) > 0 || (nighttimePrecProb?.ice?.value ?: 0) > 0) {
daytimePrecProbItems.add(Pair(R.string.precipitation_ice, daytimePrecProb?.ice))
nighttimePrecProbItems.add(Pair(R.string.precipitation_ice, nighttimePrecProb?.ice))
}
if ((daytimePrecProb?.thunderstorm ?: 0.0) > 0 || (nighttimePrecProb?.thunderstorm ?: 0.0) > 0) {
if ((daytimePrecProb?.thunderstorm?.value ?: 0) > 0 || (nighttimePrecProb?.thunderstorm?.value ?: 0) > 0) {
daytimePrecProbItems.add(Pair(R.string.precipitation_thunderstorm, daytimePrecProb?.thunderstorm))
nighttimePrecProbItems.add(Pair(R.string.precipitation_thunderstorm, nighttimePrecProb?.thunderstorm))
}
@ -689,15 +691,14 @@ internal fun PrecipitationProbabilityDetails(
@Composable
internal fun DailyPrecipitationProbabilityDetail(
item: Pair<Int, Double?>,
item: Pair<Int, Ratio?>,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
DetailsItem(
headlineText = stringResource(item.first),
supportingText = item.second?.let { pp -> UnitUtils.formatPercent(context, pp) }
?: stringResource(R.string.null_data_text),
supportingText = item.second?.formatPercent(context) ?: stringResource(R.string.null_data_text),
modifier = modifier
.padding(top = dimensionResource(R.dimen.normal_margin))
.semantics(mergeDescendants = true) {}
@ -705,7 +706,7 @@ internal fun DailyPrecipitationProbabilityDetail(
item.second?.let { pp ->
contentDescription = context.getString(item.first) +
context.getString(R.string.colon_separator) +
UnitUtils.formatPercent(context, pp)
pp.formatPercent(context)
}
}
)

View file

@ -247,60 +247,65 @@ private fun PressureChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ -> value.toPressure(pressureUnit).formatMeasure(context) },
persistentListOf(
persistentMapOf(
1080.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(48, 8, 24),
1046.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(111, 24, 64),
1038.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(142, 47, 57),
1030.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(159, 81, 44),
1024.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(163, 116, 67),
1019.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(167, 147, 107),
1015.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(176, 174, 152),
1013.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(182, 182, 182),
1011.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(155, 183, 172),
1007.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(103, 162, 155),
1002.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(26, 140, 147),
995.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 117, 146),
986.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 90, 148),
976.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 52, 146),
950.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 32, 96),
900.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(8, 16, 48)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember { { _, value, _ -> value.toPressure(pressureUnit).formatMeasure(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
1080.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(48, 8, 24),
1046.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(111, 24, 64),
1038.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(142, 47, 57),
1030.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(159, 81, 44),
1024.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(163, 116, 67),
1019.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(167, 147, 107),
1015.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(176, 174, 152),
1013.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(182, 182, 182),
1011.25.hectopascals.toDouble(pressureUnit).toFloat() to Color(155, 183, 172),
1007.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(103, 162, 155),
1002.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(26, 140, 147),
995.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 117, 146),
986.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 90, 148),
976.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 52, 146),
950.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(0, 32, 96),
900.0.hectopascals.toDouble(pressureUnit).toFloat() to Color(8, 16, 48)
)
)
),
trendHorizontalLines = persistentMapOf(
PressureUnit.NORMAL.pascals.toDouble(pressureUnit) to stringResource(R.string.temperature_normal_short)
),
},
trendHorizontalLines = remember {
persistentMapOf(
PressureUnit.NORMAL.pascals.toDouble(pressureUnit) to
context.getString(R.string.temperature_normal_short)
)
},
minY = minY,
topAxisValueFormatter = { _, value, _ ->
val currentIndex = mappedValues.keys.indexOfFirst { it == value.toLong() }.let {
if (it == 0) 1 else it
}
val previousValue = if (currentIndex > 0) {
mappedValues.values.elementAt(currentIndex - 1)
} else {
return@BreezyLineChart "-"
}
val currentValue = mappedValues.values.elementAt(currentIndex)
val trend = with(currentValue.value - previousValue.value) {
when {
// Take into account the trend if the difference is of at least 0.5
this >= 0.5 -> ""
this <= -0.5 -> ""
else -> "="
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
val currentIndex = mappedValues.keys.indexOfFirst { it == value.toLong() }.let {
if (it == 0) 1 else it
}
if (currentIndex > 0) {
val previousValue = mappedValues.values.elementAt(currentIndex - 1)
val currentValue = mappedValues.values.elementAt(currentIndex)
val trend = with(currentValue.value - previousValue.value) {
when {
// Take into account the trend if the difference is of at least 0.5
this >= 0.5 -> ""
this <= -0.5 -> ""
else -> "="
}
}
SpannableString(trend).apply {
setSpan(RelativeSizeSpan(2f), 0, trend.length, 0)
}
} else {
"-"
}
}
SpannableString(trend).apply {
setSpan(RelativeSizeSpan(2f), 0, trend.length, 0)
}
},
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ chartStep })
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ chartStep }) },
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -40,7 +40,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -64,6 +63,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap
import org.breezyweather.R
import org.breezyweather.common.extensions.getColorResource
import org.breezyweather.common.extensions.getFormattedTime
import org.breezyweather.common.extensions.is12Hour
import org.breezyweather.common.extensions.toDate
@ -267,33 +267,37 @@ private fun UVChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ -> UnitUtils.formatInt(context, value.roundToInt()) },
persistentListOf(
persistentMapOf(
19f to Color(255, 255, 255),
11f to colorResource(R.color.colorLevel_5),
10f to colorResource(R.color.colorLevel_4),
7f to colorResource(R.color.colorLevel_3),
5f to colorResource(R.color.colorLevel_2),
2f to colorResource(R.color.colorLevel_1),
0f to Color(110, 110, 110)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember { { _, value, _ -> UnitUtils.formatInt(context, value.roundToInt()) } },
colors = remember {
persistentListOf(
persistentMapOf(
19f to Color(255, 255, 255),
11f to context.getColorResource(R.color.colorLevel_5),
10f to context.getColorResource(R.color.colorLevel_4),
7f to context.getColorResource(R.color.colorLevel_3),
5f to context.getColorResource(R.color.colorLevel_2),
2f to context.getColorResource(R.color.colorLevel_1),
0f to Color(110, 110, 110)
)
)
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.index?.roundToInt()
?.let { UnitUtils.formatInt(context, it) }
?: "-"
},
trendHorizontalLines = persistentMapOf(
UV.UV_INDEX_MIDDLE to context.getString(R.string.uv_alert_level)
),
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ 1.0 }) // Every rounded UVI
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.index?.roundToInt()
?.let { UnitUtils.formatInt(context, it) }
?: "-"
}
},
trendHorizontalLines = remember {
persistentMapOf(
UV.UV_INDEX_MIDDLE to context.getString(R.string.uv_alert_level)
)
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ 1.0 }) }, // Every rounded UVI
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -312,30 +312,32 @@ private fun VisibilityChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxYRounded,
{ _, value, _ -> value.toDistance(distanceUnit).formatMeasure(context) },
persistentListOf(
persistentMapOf(
20000.meters.toDouble(distanceUnit).toFloat() to Color(119, 141, 120),
15000.meters.toDouble(distanceUnit).toFloat() to Color(91, 167, 99),
9000.meters.toDouble(distanceUnit).toFloat() to Color(90, 169, 90),
8000.meters.toDouble(distanceUnit).toFloat() to Color(98, 122, 160),
6000.meters.toDouble(distanceUnit).toFloat() to Color(98, 122, 160),
5000.meters.toDouble(distanceUnit).toFloat() to Color(167, 91, 91),
2200.meters.toDouble(distanceUnit).toFloat() to Color(167, 91, 91),
1600.meters.toDouble(distanceUnit).toFloat() to Color(162, 97, 160),
0.meters.toDouble(distanceUnit).toFloat() to Color(166, 93, 165)
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxYRounded,
endAxisValueFormatter = remember { { _, value, _ -> value.toDistance(distanceUnit).formatMeasure(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
20000.meters.toDouble(distanceUnit).toFloat() to Color(119, 141, 120),
15000.meters.toDouble(distanceUnit).toFloat() to Color(91, 167, 99),
9000.meters.toDouble(distanceUnit).toFloat() to Color(90, 169, 90),
8000.meters.toDouble(distanceUnit).toFloat() to Color(98, 122, 160),
6000.meters.toDouble(distanceUnit).toFloat() to Color(98, 122, 160),
5000.meters.toDouble(distanceUnit).toFloat() to Color(167, 91, 91),
2200.meters.toDouble(distanceUnit).toFloat() to Color(167, 91, 91),
1600.meters.toDouble(distanceUnit).toFloat() to Color(162, 97, 160),
0.meters.toDouble(distanceUnit).toFloat() to Color(166, 93, 165)
)
)
),
topAxisValueFormatter = { _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.formatValue(context) ?: "-"
},
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ step })
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
mappedValues.getOrElse(value.toLong()) { null }?.formatValue(context) ?: "-"
}
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ step }) },
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -42,7 +42,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -73,6 +72,7 @@ import org.breezyweather.common.extensions.currentLocale
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.getBeaufortScaleColor
import org.breezyweather.common.extensions.getBeaufortScaleStrength
import org.breezyweather.common.extensions.getColorResource
import org.breezyweather.common.extensions.getFormattedTime
import org.breezyweather.common.extensions.is12Hour
import org.breezyweather.common.extensions.roundUpToNearestMultiplier
@ -354,88 +354,105 @@ private fun WindChart(
}
BreezyLineChart(
location,
modelProducer,
daily.date,
maxY,
{ _, value, _ -> value.toSpeed(speedUnit).formatMeasure(context) },
persistentListOf(
persistentMapOf(
104.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(128, 128, 128),
77.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(205, 202, 112),
51.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(219, 212, 135),
46.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(231, 215, 215),
36.0.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf12),
30.5.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf11),
26.4.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf10),
24.475.metersPerSecond.toDouble(speedUnit).toFloat() to Color(109, 97, 163),
22.55.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf9),
18.9.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf8),
17.175.metersPerSecond.toDouble(speedUnit).toFloat() to Color(129, 58, 78),
15.45.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf7),
13.85.metersPerSecond.toDouble(speedUnit).toFloat() to Color(159, 127, 58),
12.25.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf6),
9.3.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf5),
6.7.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf4),
4.4.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf3),
2.4.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf2),
1.0.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf1),
0.0.metersPerSecond.toDouble(speedUnit).toFloat() to colorResource(R.color.windStrength_bf0)
),
persistentMapOf(
104.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(128, 128, 128, 160),
77.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(205, 202, 112, 160),
51.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(219, 212, 135, 160),
46.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(231, 215, 215, 160),
36.0.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf12).copy(alpha = 160f.div(255f)),
30.5.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf11).copy(alpha = 160f.div(255f)),
26.4.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf10).copy(alpha = 160f.div(255f)),
24.475.metersPerSecond.toDouble(speedUnit).toFloat() to Color(109, 97, 163, 160),
22.55.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf9).copy(alpha = 160f.div(255f)),
18.9.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf8).copy(alpha = 160f.div(255f)),
17.175.metersPerSecond.toDouble(speedUnit).toFloat() to Color(129, 58, 78, 160),
15.45.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf7).copy(alpha = 160f.div(255f)),
13.85.metersPerSecond.toDouble(speedUnit).toFloat() to Color(159, 127, 58, 160),
12.25.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf6).copy(alpha = 160f.div(255f)),
9.3.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf5).copy(alpha = 160f.div(255f)),
6.7.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf4).copy(alpha = 160f.div(255f)),
4.4.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf3).copy(alpha = 160f.div(255f)),
2.4.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf2).copy(alpha = 160f.div(255f)),
1.0.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf1).copy(alpha = 160f.div(255f)),
0.0.metersPerSecond.toDouble(speedUnit).toFloat() to
colorResource(R.color.windStrength_bf0).copy(alpha = 160f.div(255f))
location = location,
modelProducer = modelProducer,
theDay = daily.date,
maxY = maxY,
endAxisValueFormatter = remember { { _, value, _ -> value.toSpeed(speedUnit).formatMeasure(context) } },
colors = remember {
persistentListOf(
persistentMapOf(
104.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(128, 128, 128),
77.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(205, 202, 112),
51.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(219, 212, 135),
46.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(231, 215, 215),
36.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf12),
30.5.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf11),
26.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf10),
24.475.metersPerSecond.toDouble(speedUnit).toFloat() to Color(109, 97, 163),
22.55.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf9),
18.9.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf8),
17.175.metersPerSecond.toDouble(speedUnit).toFloat() to Color(129, 58, 78),
15.45.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf7),
13.85.metersPerSecond.toDouble(speedUnit).toFloat() to Color(159, 127, 58),
12.25.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf6),
9.3.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf5),
6.7.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf4),
4.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf3),
2.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf2),
1.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf1),
0.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf0)
),
persistentMapOf(
104.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(128, 128, 128, 160),
77.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(205, 202, 112, 160),
51.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(219, 212, 135, 160),
46.0.metersPerSecond.toDouble(speedUnit).toFloat() to Color(231, 215, 215, 160),
36.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf12).copy(alpha = 160f.div(255f)),
30.5.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf11).copy(alpha = 160f.div(255f)),
26.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf10).copy(alpha = 160f.div(255f)),
24.475.metersPerSecond.toDouble(speedUnit).toFloat() to Color(109, 97, 163, 160),
22.55.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf9).copy(alpha = 160f.div(255f)),
18.9.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf8).copy(alpha = 160f.div(255f)),
17.175.metersPerSecond.toDouble(speedUnit).toFloat() to Color(129, 58, 78, 160),
15.45.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf7).copy(alpha = 160f.div(255f)),
13.85.metersPerSecond.toDouble(speedUnit).toFloat() to Color(159, 127, 58, 160),
12.25.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf6).copy(alpha = 160f.div(255f)),
9.3.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf5).copy(alpha = 160f.div(255f)),
6.7.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf4).copy(alpha = 160f.div(255f)),
4.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf3).copy(alpha = 160f.div(255f)),
2.4.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf2).copy(alpha = 160f.div(255f)),
1.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf1).copy(alpha = 160f.div(255f)),
0.0.metersPerSecond.toDouble(speedUnit).toFloat() to
context.getColorResource(R.color.windStrength_bf0).copy(alpha = 160f.div(255f))
)
)
),
topAxisValueFormatter = { _, value, _ ->
val arrow = mappedValues.getOrElse(value.toLong()) { null }?.arrow ?: "-"
SpannableString(arrow).apply {
setSpan(RelativeSizeSpan(2f), 0, arrow.length, 0)
},
topAxisValueFormatter = remember(mappedValues) {
{ _, value, _ ->
val arrow = mappedValues.getOrElse(value.toLong()) { null }?.arrow ?: "-"
SpannableString(arrow).apply {
setSpan(RelativeSizeSpan(2f), 0, arrow.length, 0)
}
}
},
trendHorizontalLines = buildMap {
if (maxY > 7.beaufort.toDouble(speedUnit)) {
put(7.beaufort.toDouble(speedUnit), 7.beaufort.getBeaufortScaleStrength(context)!!)
}
// TODO: Make this a const:
if (maxY < (7.beaufort.inMetersPerSecond + 5.0).metersPerSecond.toDouble(speedUnit)) {
put(3.beaufort.toDouble(speedUnit), 3.beaufort.getBeaufortScaleStrength(context)!!)
}
}.toImmutableMap(),
endAxisItemPlacer = remember {
VerticalAxis.ItemPlacer.step({ step })
trendHorizontalLines = remember {
buildMap {
if (maxY > 7.beaufort.toDouble(speedUnit)) {
put(7.beaufort.toDouble(speedUnit), 7.beaufort.getBeaufortScaleStrength(context)!!)
}
// TODO: Make this a const:
if (maxY < (7.beaufort.inMetersPerSecond + 5.0).metersPerSecond.toDouble(speedUnit)) {
put(3.beaufort.toDouble(speedUnit), 3.beaufort.getBeaufortScaleStrength(context)!!)
}
}.toImmutableMap()
},
endAxisItemPlacer = remember { VerticalAxis.ItemPlacer.step({ step }) },
markerVisibilityListener = markerVisibilityListener
)
}

View file

@ -25,8 +25,8 @@ import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.common.utils.helpers.IntentHelper
import org.breezyweather.ui.theme.resource.providers.ResourceProvider
import org.breezyweather.unit.formatting.UnitWidth
@ -50,13 +50,13 @@ class HumidityViewHolder(parent: ViewGroup) : AbstractMainCardViewHolder(
val talkBackBuilder = StringBuilder(context.getString(R.string.humidity))
location.weather!!.current?.let { current ->
current.relativeHumidity?.let { relativeHumidity ->
humidityValueView.text = UnitUtils.formatPercent(context, relativeHumidity)
humidityValueView.text = relativeHumidity.formatPercent(context, UnitWidth.NARROW)
if (relativeHumidity in 0.0..100.0) {
if (relativeHumidity.inPercent in 0.0..100.0) {
wavesBackgroundView.setImageDrawable(
AppCompatResources.getDrawable(
context,
when (relativeHumidity) {
when (relativeHumidity.inPercent) {
in 0.0..20.0 -> R.drawable.humidity_percent_7
in 20.0..40.0 -> R.drawable.humidity_percent_30
in 60.0..80.0 -> R.drawable.humidity_percent_75

View file

@ -158,7 +158,7 @@ class DailyPrecipitationAdapter(
val keyLineList = mutableListOf<TrendRecyclerView.KeyLine>()
keyLineList.add(
TrendRecyclerView.KeyLine(
Precipitation.PRECIPITATION_HALF_DAY_LIGHT.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_LIGHT.millimeters.inMicrometers.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_LIGHT.millimeters.formatValue(activity),
activity.getString(R.string.precipitation_intensity_light),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE
@ -166,7 +166,7 @@ class DailyPrecipitationAdapter(
)
keyLineList.add(
TrendRecyclerView.KeyLine(
Precipitation.PRECIPITATION_HALF_DAY_HEAVY.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_HEAVY.millimeters.inMicrometers.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_HEAVY.millimeters.formatValue(activity),
activity.getString(R.string.precipitation_intensity_heavy),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE
@ -174,7 +174,7 @@ class DailyPrecipitationAdapter(
)
keyLineList.add(
TrendRecyclerView.KeyLine(
-Precipitation.PRECIPITATION_HALF_DAY_LIGHT.toFloat(),
-Precipitation.PRECIPITATION_HALF_DAY_LIGHT.millimeters.inMicrometers.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_LIGHT.millimeters.formatValue(activity),
activity.getString(R.string.precipitation_intensity_light),
TrendRecyclerView.KeyLine.ContentPosition.BELOW_LINE
@ -182,7 +182,7 @@ class DailyPrecipitationAdapter(
)
keyLineList.add(
TrendRecyclerView.KeyLine(
-Precipitation.PRECIPITATION_HALF_DAY_HEAVY.toFloat(),
-Precipitation.PRECIPITATION_HALF_DAY_HEAVY.millimeters.inMicrometers.toFloat(),
Precipitation.PRECIPITATION_HALF_DAY_HEAVY.millimeters.formatValue(activity),
activity.getString(R.string.precipitation_intensity_heavy),
TrendRecyclerView.KeyLine.ContentPosition.BELOW_LINE

View file

@ -26,6 +26,7 @@ import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getCalendarMonth
import org.breezyweather.common.extensions.getThemeColor
import org.breezyweather.common.options.appearance.DetailScreen
@ -84,7 +85,7 @@ class DailyTemperatureAdapter(
talkBackBuilder.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
.append(activity.getString(R.string.precipitation_probability))
.append(activity.getString(R.string.colon_separator))
.append(UnitUtils.formatPercent(activity, p))
.append(p.formatPercent(activity))
}
}
}
@ -104,7 +105,7 @@ class DailyTemperatureAdapter(
talkBackBuilder.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
.append(activity.getString(R.string.precipitation_probability))
.append(activity.getString(R.string.colon_separator))
.append(UnitUtils.formatPercent(activity, p))
.append(p.formatPercent(activity))
}
}
}
@ -112,15 +113,10 @@ class DailyTemperatureAdapter(
daily.day?.weatherCode?.let { ResourceHelper.getWeatherIcon(mResourceProvider, it, true) },
missingIconVisibility = View.INVISIBLE
)
val daytimePrecipitationProbability = daily.day?.precipitationProbability?.total?.toFloat()
val nighttimePrecipitationProbability = daily.night?.precipitationProbability?.total?.toFloat()
var p: Float = max(
daytimePrecipitationProbability ?: 0f,
nighttimePrecipitationProbability ?: 0f
)
if (!mShowPrecipitationProbability) {
p = 0f
}
val daytimePrecipitationProbability = daily.day?.precipitationProbability?.total
val nighttimePrecipitationProbability = daily.night?.precipitationProbability?.total
val p = listOfNotNull(daytimePrecipitationProbability, nighttimePrecipitationProbability)
.takeIf { it.isNotEmpty() }?.maxBy { it.value }
mPolylineAndHistogramView.setData(
buildTemperatureArrayForItem(mDaytimeTemperatures, position),
buildTemperatureArrayForItem(mNighttimeTemperatures, position),
@ -136,12 +132,8 @@ class DailyTemperatureAdapter(
),
mHighestTemperature,
mLowestTemperature,
if (p > 0) p else null,
if (p > 0) {
UnitUtils.formatPercent(activity, p.toDouble())
} else {
null
},
p?.takeIf { it.value > 0 && mShowPrecipitationProbability }?.inPercent?.toFloat(),
p?.takeIf { it.value > 0 && mShowPrecipitationProbability }?.formatPercent(activity, UnitWidth.NARROW),
100f,
0f
)

View file

@ -18,22 +18,26 @@ package org.breezyweather.ui.main.adapters.trend.hourly
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.CLOUD_COVER_FEW
import org.breezyweather.common.extensions.CLOUD_COVER_SCT
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getCloudCoverColor
import org.breezyweather.common.extensions.getCloudCoverDescription
import org.breezyweather.common.extensions.getThemeColor
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.domain.weather.model.CLOUD_COVER_CLEAR
import org.breezyweather.domain.weather.model.CLOUD_COVER_PARTLY
import org.breezyweather.domain.weather.model.getCloudCoverColor
import org.breezyweather.ui.common.widgets.trend.TrendRecyclerView
import org.breezyweather.ui.common.widgets.trend.chart.PolylineAndHistogramView
import org.breezyweather.ui.theme.ThemeManager
import org.breezyweather.ui.theme.weatherView.WeatherViewController
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.ratio.Ratio.Companion.percent
/**
* Hourly Cloud Cover adapter.
@ -60,7 +64,7 @@ class HourlyCloudCoverAdapter(
hourly.cloudCover?.let { cloudCover ->
talkBackBuilder
.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
.append(UnitUtils.formatPercent(activity, cloudCover.toDouble()))
.append(cloudCover.formatPercent(activity, UnitWidth.NARROW))
}
mPolylineAndHistogramView.setData(
null,
@ -69,14 +73,14 @@ class HourlyCloudCoverAdapter(
null,
null,
null,
hourly.cloudCover?.toFloat() ?: 0f,
hourly.cloudCover?.let { UnitUtils.formatPercent(activity, it.toDouble()) },
hourly.cloudCover?.inPercent?.toFloat() ?: 0f,
hourly.cloudCover?.formatPercent(activity, UnitWidth.NARROW),
100f,
0f
)
mPolylineAndHistogramView.setLineColors(
hourly.getCloudCoverColor(activity),
hourly.getCloudCoverColor(activity),
hourly.cloudCover?.getCloudCoverColor(activity) ?: Color.TRANSPARENT,
hourly.cloudCover?.getCloudCoverColor(activity) ?: Color.TRANSPARENT,
activity.getThemeColor(com.google.android.material.R.attr.colorOutline)
)
@ -108,7 +112,7 @@ class HourlyCloudCoverAdapter(
init {
mHighestCloudCover = location.weather!!.nextHourlyForecast
.mapNotNull { it.cloudCover }
.mapNotNull { it.cloudCover?.inPercent }
.maxOrNull()
?.toFloat() ?: 0f
}
@ -132,18 +136,18 @@ class HourlyCloudCoverAdapter(
val keyLineList = mutableListOf<TrendRecyclerView.KeyLine>()
keyLineList.add(
TrendRecyclerView.KeyLine(
CLOUD_COVER_PARTLY.toFloat(),
UnitUtils.formatPercent(activity, CLOUD_COVER_PARTLY),
activity.getString(R.string.weather_kind_partly_cloudy),
CLOUD_COVER_FEW.toFloat(),
CLOUD_COVER_FEW.percent.formatPercent(activity),
CLOUD_COVER_FEW.percent.getCloudCoverDescription(activity),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE
)
)
keyLineList.add(
TrendRecyclerView.KeyLine(
CLOUD_COVER_CLEAR.toFloat(),
UnitUtils.formatPercent(activity, CLOUD_COVER_CLEAR),
activity.getString(R.string.weather_kind_clear),
TrendRecyclerView.KeyLine.ContentPosition.BELOW_LINE
CLOUD_COVER_SCT.toFloat(),
CLOUD_COVER_SCT.percent.formatPercent(activity),
CLOUD_COVER_SCT.percent.getCloudCoverDescription(activity),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE
)
)
host.setData(keyLineList, 100f, 0f)

View file

@ -25,6 +25,7 @@ import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getThemeColor
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
@ -66,7 +67,7 @@ class HourlyHumidityAdapter(
talkBackBuilder.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
.append(activity.getString(R.string.humidity))
.append(activity.getString(R.string.colon_separator))
.append(UnitUtils.formatPercent(activity, it))
.append(it.formatPercent(activity))
}
hourly.dewPoint?.let {
talkBackBuilder.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
@ -91,8 +92,8 @@ class HourlyHumidityAdapter(
null,
mHighestDewPoint,
mLowestDewPoint,
hourly.relativeHumidity?.toFloat(),
hourly.relativeHumidity?.let { UnitUtils.formatPercent(activity, it) },
hourly.relativeHumidity?.inPercent?.toFloat(),
hourly.relativeHumidity?.formatPercent(activity, UnitWidth.NARROW),
100f,
0f
)

View file

@ -155,7 +155,7 @@ class HourlyPrecipitationAdapter(
val keyLineList = mutableListOf<TrendRecyclerView.KeyLine>()
keyLineList.add(
TrendRecyclerView.KeyLine(
Precipitation.PRECIPITATION_HOURLY_LIGHT.toFloat(),
Precipitation.PRECIPITATION_HOURLY_LIGHT.millimeters.inMicrometers.toFloat(),
activity.getString(R.string.precipitation_intensity_light),
Precipitation.PRECIPITATION_HALF_DAY_LIGHT.millimeters.formatValue(activity),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE
@ -163,7 +163,7 @@ class HourlyPrecipitationAdapter(
)
keyLineList.add(
TrendRecyclerView.KeyLine(
Precipitation.PRECIPITATION_HOURLY_HEAVY.toFloat(),
Precipitation.PRECIPITATION_HOURLY_HEAVY.millimeters.inMicrometers.toFloat(),
activity.getString(R.string.precipitation_intensity_heavy),
Precipitation.PRECIPITATION_HALF_DAY_HEAVY.millimeters.formatValue(activity),
TrendRecyclerView.KeyLine.ContentPosition.ABOVE_LINE

View file

@ -25,10 +25,10 @@ import breezyweather.domain.location.model.Location
import org.breezyweather.R
import org.breezyweather.common.activities.BreezyActivity
import org.breezyweather.common.extensions.formatMeasure
import org.breezyweather.common.extensions.formatPercent
import org.breezyweather.common.extensions.getCalendarMonth
import org.breezyweather.common.extensions.getThemeColor
import org.breezyweather.common.options.appearance.DetailScreen
import org.breezyweather.common.utils.UnitUtils
import org.breezyweather.ui.common.widgets.trend.TrendRecyclerView
import org.breezyweather.ui.common.widgets.trend.chart.PolylineAndHistogramView
import org.breezyweather.ui.theme.ThemeManager
@ -80,15 +80,12 @@ class HourlyTemperatureAdapter(
},
missingIconVisibility = View.INVISIBLE
)
val precipitationProbability = hourly.precipitationProbability?.total
var p: Float = precipitationProbability?.toFloat() ?: 0f
if (!mShowPrecipitationProbability) {
p = 0f
} else if (hourly.precipitationProbability?.total != null) {
val p = hourly.precipitationProbability?.total
if (mShowPrecipitationProbability && hourly.precipitationProbability?.total != null) {
talkBackBuilder.append(activity.getString(org.breezyweather.unit.R.string.locale_separator))
.append(activity.getString(R.string.precipitation_probability))
.append(activity.getString(R.string.colon_separator))
.append(UnitUtils.formatPercent(activity, p.toDouble()))
.append(hourly.precipitationProbability!!.total!!.formatPercent(activity, UnitWidth.NARROW))
}
mPolylineAndHistogramView.setData(
buildTemperatureArrayForItem(mTemperatures, position),
@ -101,12 +98,8 @@ class HourlyTemperatureAdapter(
null,
mHighestTemperature,
mLowestTemperature,
if (p > 0) p else null,
if (p > 0) {
UnitUtils.formatPercent(activity, p.toDouble())
} else {
null
},
p?.takeIf { it.value > 0 && mShowPrecipitationProbability }?.inPercent?.toFloat(),
p?.takeIf { it.value > 0 && mShowPrecipitationProbability }?.formatPercent(activity, UnitWidth.NARROW),
100f,
0f
)

View file

@ -98,6 +98,7 @@ import org.breezyweather.unit.precipitation.Precipitation.Companion.inches
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.precipitation.PrecipitationUnit
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.speed.Speed.Companion.milesPerHour
@ -432,10 +433,10 @@ class AccuService @Inject constructor(
gusts = currentResult.WindGust?.Speed?.Metric?.Value?.kilometersPerHour
),
uV = UV(index = currentResult.UVIndex?.toDouble()),
relativeHumidity = currentResult.RelativeHumidity?.toDouble(),
relativeHumidity = currentResult.RelativeHumidity?.percent,
dewPoint = currentResult.DewPoint?.Metric?.Value?.celsius,
pressure = currentResult.Pressure?.Metric?.Value?.hectopascals,
cloudCover = currentResult.CloudCover,
cloudCover = currentResult.CloudCover?.percent,
visibility = currentResult.Visibility?.Metric?.Value?.kilometers,
ceiling = currentResult.Ceiling?.Metric?.Value?.meters,
dailyForecast = dailyResult?.Headline?.Text,
@ -465,11 +466,11 @@ class AccuService @Inject constructor(
ice = getQuantity(forecasts.Day?.Ice)
),
precipitationProbability = PrecipitationProbability(
total = forecasts.Day?.PrecipitationProbability?.toDouble(),
thunderstorm = forecasts.Day?.ThunderstormProbability?.toDouble(),
rain = forecasts.Day?.RainProbability?.toDouble(),
snow = forecasts.Day?.SnowProbability?.toDouble(),
ice = forecasts.Day?.IceProbability?.toDouble()
total = forecasts.Day?.PrecipitationProbability?.percent,
thunderstorm = forecasts.Day?.ThunderstormProbability?.percent,
rain = forecasts.Day?.RainProbability?.percent,
snow = forecasts.Day?.SnowProbability?.percent,
ice = forecasts.Day?.IceProbability?.percent
),
precipitationDuration = PrecipitationDuration(
total = forecasts.Day?.HoursOfPrecipitation?.hours,
@ -498,11 +499,11 @@ class AccuService @Inject constructor(
ice = getQuantity(forecasts.Night?.Ice)
),
precipitationProbability = PrecipitationProbability(
total = forecasts.Night?.PrecipitationProbability?.toDouble(),
thunderstorm = forecasts.Night?.ThunderstormProbability?.toDouble(),
rain = forecasts.Night?.RainProbability?.toDouble(),
snow = forecasts.Night?.SnowProbability?.toDouble(),
ice = forecasts.Night?.IceProbability?.toDouble()
total = forecasts.Night?.PrecipitationProbability?.percent,
thunderstorm = forecasts.Night?.ThunderstormProbability?.percent,
rain = forecasts.Night?.RainProbability?.percent,
snow = forecasts.Night?.SnowProbability?.percent,
ice = forecasts.Night?.IceProbability?.percent
),
precipitationDuration = PrecipitationDuration(
total = forecasts.Night?.HoursOfPrecipitation?.hours,
@ -588,11 +589,11 @@ class AccuService @Inject constructor(
ice = getQuantity(result.Ice)
),
precipitationProbability = PrecipitationProbability(
total = result.PrecipitationProbability?.toDouble(),
thunderstorm = result.ThunderstormProbability?.toDouble(),
rain = result.RainProbability?.toDouble(),
snow = result.SnowProbability?.toDouble(),
ice = result.IceProbability?.toDouble()
total = result.PrecipitationProbability?.percent,
thunderstorm = result.ThunderstormProbability?.percent,
rain = result.RainProbability?.percent,
snow = result.SnowProbability?.percent,
ice = result.IceProbability?.percent
),
wind = Wind(
degree = result.Wind?.Direction?.Degrees?.toDouble(),
@ -600,9 +601,9 @@ class AccuService @Inject constructor(
gusts = getSpeedInMetersPerSecond(result.WindGust?.Speed)
),
uV = UV(index = result.UVIndex?.toDouble()),
relativeHumidity = result.RelativeHumidity?.toDouble(),
relativeHumidity = result.RelativeHumidity?.percent,
dewPoint = getTemperature(result.DewPoint),
cloudCover = result.CloudCover,
cloudCover = result.CloudCover?.percent,
visibility = getDistance(result.Visibility)
)
}

View file

@ -64,6 +64,7 @@ import org.breezyweather.sources.getWindDegree
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
@ -275,7 +276,7 @@ class AemetService @Inject constructor(
speed = it.vv?.metersPerSecond,
gusts = it.vmax?.metersPerSecond
),
relativeHumidity = it.hr,
relativeHumidity = it.hr?.percent,
dewPoint = it.tpr?.celsius,
pressure = it.pres?.hectopascals,
visibility = it.vis?.meters
@ -366,7 +367,7 @@ class AemetService @Inject constructor(
feelsLike = maxAtMap.getOrElse(key) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = ppMap.getOrElse(key) { null }
total = ppMap.getOrElse(key) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(key) { null },
@ -382,7 +383,7 @@ class AemetService @Inject constructor(
feelsLike = minAtMap.getOrElse(key) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = ppMap.getOrElse(key) { null }
total = ppMap.getOrElse(key) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(key) { null },
@ -391,8 +392,8 @@ class AemetService @Inject constructor(
)
),
relativeHumidity = DailyRelativeHumidity(
max = maxRhMap.getOrElse(key) { null },
min = minRhMap.getOrElse(key) { null }
max = maxRhMap.getOrElse(key) { null }?.percent,
min = minRhMap.getOrElse(key) { null }?.percent
),
uV = UV(
index = uviMap.getOrElse(key) { null }
@ -519,16 +520,16 @@ class AemetService @Inject constructor(
snow = snMap.getOrElse(key) { null }?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = ppMap.getOrElse(key) { null },
thunderstorm = ptMap.getOrElse(key) { null },
snow = psMap.getOrElse(key) { null }
total = ppMap.getOrElse(key) { null }?.percent,
thunderstorm = ptMap.getOrElse(key) { null }?.percent,
snow = psMap.getOrElse(key) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(key) { null },
speed = wsMap.getOrElse(key) { null }?.kilometersPerHour,
gusts = wgMap.getOrElse(key) { null }?.kilometersPerHour
),
relativeHumidity = rhMap.getOrElse(key) { null }
relativeHumidity = rhMap.getOrElse(key) { null }?.percent
)
)
}

View file

@ -49,6 +49,7 @@ import org.breezyweather.common.source.WeatherSource.Companion.PRIORITY_NONE
import org.breezyweather.sources.bmd.json.BmdData
import org.breezyweather.sources.bmd.json.BmdForecastResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -241,10 +242,10 @@ class BmdService @Inject constructor(
)
),
relativeHumidity = rhMap.getOrElse(key) { null }?.let {
DailyRelativeHumidity(average = it)
DailyRelativeHumidity(average = it.percent)
},
cloudCover = ccMap.getOrElse(key) { null }?.let {
DailyCloudCover(average = it)
DailyCloudCover(average = it.percent)
}
)
}
@ -320,8 +321,8 @@ class BmdService @Inject constructor(
speed = wsMap.getOrElse(key) { null }?.kilometersPerHour,
gusts = wgMap.getOrElse(key) { null }?.kilometersPerHour
),
relativeHumidity = rhMap.getOrElse(key) { null },
cloudCover = ccMap.getOrElse(key) { null }
relativeHumidity = rhMap.getOrElse(key) { null }?.percent,
cloudCover = ccMap.getOrElse(key) { null }?.percent
)
)
}

View file

@ -64,6 +64,7 @@ import org.breezyweather.sources.bmkg.json.BmkgWarningResult
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -259,7 +260,7 @@ class BmkgService @Inject constructor(
degree = currentResult.data?.cuaca?.wdDeg,
speed = currentResult.data?.cuaca?.ws?.kilometersPerHour
),
relativeHumidity = currentResult.data?.cuaca?.hu,
relativeHumidity = currentResult.data?.cuaca?.hu?.percent,
visibility = currentResult.data?.cuaca?.vs?.meters
)
}
@ -310,8 +311,8 @@ class BmkgService @Inject constructor(
degree = it.wdDeg,
speed = it.ws?.kilometersPerHour
),
relativeHumidity = it.hu,
cloudCover = it.tcc?.toInt(),
relativeHumidity = it.hu?.percent,
cloudCover = it.tcc?.percent,
visibility = it.vs?.meters
)
)

View file

@ -64,6 +64,8 @@ import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgr
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.milligramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -281,7 +283,7 @@ class ChinaService @Inject constructor(
null
},
relativeHumidity = if (!current.humidity?.value.isNullOrEmpty()) {
current.humidity.value.toDoubleOrNull()
current.humidity.value.toDoubleOrNull()?.percent
} else {
null
},
@ -368,12 +370,12 @@ class ChinaService @Inject constructor(
return dailyList
}
private fun getPrecipitationProbability(forecast: ChinaForecastDaily, index: Int): Double? {
private fun getPrecipitationProbability(forecast: ChinaForecastDaily, index: Int): Ratio? {
if (forecast.precipitationProbability == null || forecast.precipitationProbability.value.isNullOrEmpty()) {
return null
}
return forecast.precipitationProbability.value.getOrNull(index)?.toDoubleOrNull()
return forecast.precipitationProbability.value.getOrNull(index)?.toDoubleOrNull()?.percent
}
private fun getHourlyList(

View file

@ -80,6 +80,7 @@ import org.breezyweather.sources.nlsc.NlscService.Companion.WUQIU_BBOX
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.beaufort
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature
@ -467,7 +468,7 @@ class CwaService @Inject constructor(
speed = windSpeed,
gusts = windGusts
),
relativeHumidity = relativeHumidity,
relativeHumidity = relativeHumidity?.percent,
pressure = computeMeanSeaLevelPressure(
barometricPressure = barometricPressure,
altitude = altitude,
@ -625,7 +626,7 @@ class CwaService @Inject constructor(
feelsLike = maxAtMap.getOrElse(dayTime) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = popMap.getOrElse(dayTime) { null }
total = popMap.getOrElse(dayTime) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(dayTime) { null },
@ -640,7 +641,7 @@ class CwaService @Inject constructor(
feelsLike = minAtMap.getOrElse(nightTime) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = popMap.getOrElse(nightTime) { null }
total = popMap.getOrElse(nightTime) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(nightTime) { null },
@ -762,13 +763,13 @@ class CwaService @Inject constructor(
feelsLike = atMap.getOrElse(key) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = popMap.getOrElse(key) { null }
total = popMap.getOrElse(key) { null }?.percent
),
wind = Wind(
degree = wdMap.getOrElse(key) { null },
speed = wsMap.getOrElse(key) { null }?.metersPerSecond
),
relativeHumidity = rhMap.getOrElse(key) { null },
relativeHumidity = rhMap.getOrElse(key) { null }?.percent,
dewPoint = tdMap.getOrElse(key) { null }?.celsius
)
)

View file

@ -51,6 +51,7 @@ import org.breezyweather.sources.dmi.json.DmiWarningResult
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -214,7 +215,7 @@ class DmiService @Inject constructor(
speed = result.windSpeed?.metersPerSecond,
gusts = result.windGust?.metersPerSecond
),
relativeHumidity = result.humidity,
relativeHumidity = result.humidity?.percent,
pressure = result.pressure?.hectopascals,
visibility = result.visibility?.meters
)

View file

@ -59,6 +59,7 @@ import org.breezyweather.sources.eccc.json.EcccUnit
import org.breezyweather.sources.getWindDegree
import org.breezyweather.unit.distance.Distance.Companion.kilometers
import org.breezyweather.unit.pressure.Pressure.Companion.kilopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -203,7 +204,7 @@ class EcccService @Inject constructor(
speed = getNonEmptyMetric(result.windSpeed)?.kilometersPerHour,
gusts = getNonEmptyMetric(result.windGust)?.kilometersPerHour
),
relativeHumidity = result.humidity?.toDoubleOrNull(),
relativeHumidity = result.humidity?.toDoubleOrNull()?.percent,
dewPoint = getNonEmptyMetric(result.dewpoint)?.celsius,
pressure = getNonEmptyMetric(result.pressure)?.kilopascals,
visibility = getNonEmptyMetric(result.visibility)?.kilometers
@ -263,7 +264,7 @@ class EcccService @Inject constructor(
temperature = daytime.temperature?.periodHigh?.celsius
),
precipitationProbability = PrecipitationProbability(
total = daytime.precip?.toDoubleOrNull()
total = daytime.precip?.toDoubleOrNull()?.percent
)
)
} else {
@ -277,7 +278,7 @@ class EcccService @Inject constructor(
temperature = nighttime.temperature?.periodLow?.celsius
),
precipitationProbability = PrecipitationProbability(
total = nighttime.precip?.toDoubleOrNull()
total = nighttime.precip?.toDoubleOrNull()?.percent
)
),
sunshineDuration = daytime?.sun?.value?.toDoubleOrNull()?.hours
@ -306,7 +307,7 @@ class EcccService @Inject constructor(
feelsLike = getNonEmptyMetric(result.feelsLike)?.celsius
),
precipitationProbability = if (!result.precip.isNullOrEmpty()) {
PrecipitationProbability(total = result.precip.toDoubleOrNull())
PrecipitationProbability(total = result.precip.toDoubleOrNull()?.percent)
} else {
null
},

View file

@ -54,6 +54,8 @@ import org.breezyweather.sources.geosphereat.json.GeoSphereAtWarningsResult
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.pascals
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -361,10 +363,9 @@ class GeoSphereAtService @Inject constructor(
} else {
null
},
relativeHumidity = hourlyResult.features[0].properties!!.parameters!!.rh2m?.data?.getOrNull(i),
relativeHumidity = hourlyResult.features[0].properties!!.parameters!!.rh2m?.data?.getOrNull(i)?.percent,
pressure = hourlyResult.features[0].properties!!.parameters!!.sp?.data?.getOrNull(i)?.pascals,
cloudCover = hourlyResult.features[0].properties!!.parameters!!.tcc?.data?.getOrNull(i)?.times(100)
?.roundToInt()
cloudCover = hourlyResult.features[0].properties!!.parameters!!.tcc?.data?.getOrNull(i)?.fraction
)
}
}

View file

@ -54,6 +54,7 @@ import org.breezyweather.sources.here.json.HereWeatherData
import org.breezyweather.unit.distance.Distance.Companion.kilometers
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -181,7 +182,7 @@ class HereService @Inject constructor(
speed = result.windSpeed?.kilometersPerHour
),
uV = UV(index = result.uvIndex?.toDouble()),
relativeHumidity = result.humidity?.toDouble(),
relativeHumidity = result.humidity?.toDoubleOrNull()?.percent,
dewPoint = result.dewPoint?.celsius,
pressure = result.barometerPressure?.hectopascals,
visibility = result.visibility?.kilometers
@ -251,14 +252,14 @@ class HereService @Inject constructor(
snow = result.snowFall?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = result.precipitationProbability?.toDouble()
total = result.precipitationProbability?.percent
),
wind = Wind(
degree = result.windDirection,
speed = result.windSpeed?.kilometersPerHour
),
uV = UV(index = result.uvIndex?.toDouble()),
relativeHumidity = result.humidity?.toDouble(),
relativeHumidity = result.humidity?.toDoubleOrNull()?.percent,
dewPoint = result.dewPoint?.celsius,
pressure = result.barometerPressure?.hectopascals,
visibility = result.visibility?.kilometers

View file

@ -63,6 +63,8 @@ import org.breezyweather.sources.hko.json.HkoNormalsResult
import org.breezyweather.sources.hko.json.HkoOneJsonResult
import org.breezyweather.sources.hko.json.HkoWarningResult
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import org.json.JSONObject
@ -337,7 +339,7 @@ class HkoService @Inject constructor(
uV = UV(
index = oneJson.RHRREAD?.UVIndex?.toDoubleOrNull()
),
relativeHumidity = regionalWeather?.RH?.Value?.toDoubleOrNull(),
relativeHumidity = regionalWeather?.RH?.Value?.toDoubleOrNull()?.percent,
pressure = regionalWeather?.Pressure?.Value?.toDoubleOrNull()?.hectopascals,
dailyForecast = oneJson.F9D?.WeatherForecast?.getOrElse(0) { null }?.ForecastWeather
)
@ -496,7 +498,7 @@ class HkoService @Inject constructor(
degree = value.ForecastWindDirection,
speed = value.ForecastWindSpeed?.kilometersPerHour
),
relativeHumidity = value.ForecastRelativeHumidity
relativeHumidity = value.ForecastRelativeHumidity?.percent
)
)
}
@ -935,7 +937,7 @@ class HkoService @Inject constructor(
private fun getPrecipitationProbability(
probability: String?,
): Double? {
): Ratio? {
return when (probability) {
"<10%" -> 10.0
"20%" -> 20.0
@ -944,7 +946,7 @@ class HkoService @Inject constructor(
"80%" -> 80.0
">90%" -> 90.0
else -> null
}
}?.percent
}
private fun getAlertColor(

View file

@ -149,32 +149,26 @@ class IlmateenistusService @Inject constructor(
context: Context,
forecastResult: IlmateenistusForecastResult,
): List<HourlyWrapper> {
val hourlyList = mutableListOf<HourlyWrapper>()
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ENGLISH)
formatter.timeZone = TimeZone.getTimeZone("Europe/Tallinn")
forecastResult.forecast?.tabular?.time?.forEach {
if (it.attributes.from != null) {
hourlyList.add(
HourlyWrapper(
date = formatter.parse(it.attributes.from)!!,
weatherText = getWeatherText(context, it.phenomen?.attributes?.className),
weatherCode = getWeatherCode(it.phenomen?.attributes?.className),
temperature = TemperatureWrapper(
temperature = it.temperature?.attributes?.value?.toDoubleOrNull()?.celsius
),
precipitation = Precipitation(
total = it.precipitation?.attributes?.value?.toDoubleOrNull()?.millimeters
),
wind = Wind(
degree = it.windDirection?.attributes?.deg?.toDoubleOrNull(),
speed = it.windSpeed?.attributes?.mps?.toDoubleOrNull()?.metersPerSecond
),
pressure = it.pressure?.attributes?.value?.toDoubleOrNull()?.hectopascals
)
)
}
}
return hourlyList
return forecastResult.forecast?.tabular?.time?.filter { it.attributes.from != null }?.map {
HourlyWrapper(
date = formatter.parse(it.attributes.from!!)!!,
weatherText = getWeatherText(context, it.phenomen?.attributes?.className),
weatherCode = getWeatherCode(it.phenomen?.attributes?.className),
temperature = TemperatureWrapper(
temperature = it.temperature?.attributes?.value?.toDoubleOrNull()?.celsius
),
precipitation = Precipitation(
total = it.precipitation?.attributes?.value?.toDoubleOrNull()?.millimeters
),
wind = Wind(
degree = it.windDirection?.attributes?.deg?.toDoubleOrNull(),
speed = it.windSpeed?.attributes?.mps?.toDoubleOrNull()?.metersPerSecond
),
pressure = it.pressure?.attributes?.value?.toDoubleOrNull()?.hectopascals
)
} ?: emptyList()
}
private fun getWeatherText(

View file

@ -39,6 +39,7 @@ import org.breezyweather.common.source.WeatherSource.Companion.PRIORITY_HIGHEST
import org.breezyweather.common.source.WeatherSource.Companion.PRIORITY_NONE
import org.breezyweather.sources.imd.json.ImdWeatherResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -278,8 +279,8 @@ class ImdService @Inject constructor(
speed = wspdMap[it]?.metersPerSecond,
gusts = gustMap[it]?.metersPerSecond
),
relativeHumidity = rhMap[it],
cloudCover = tcdcMap[it]?.toInt()
relativeHumidity = rhMap[it]?.percent,
cloudCover = tcdcMap[it]?.percent
)
)
}

View file

@ -56,6 +56,7 @@ import org.breezyweather.common.utils.helpers.LogHelper
import org.breezyweather.sources.RefreshHelper
import org.breezyweather.sources.ims.json.ImsLocation
import org.breezyweather.sources.ims.json.ImsWeatherData
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -227,7 +228,7 @@ class ImsService @Inject constructor(
feelsLike = hourlyResult.value.windChill?.toDoubleOrNull()?.celsius
),
precipitationProbability = PrecipitationProbability(
total = hourlyResult.value.rainChance?.toDoubleOrNull()
total = hourlyResult.value.rainChance?.toDoubleOrNull()?.percent
),
wind = hourlyResult.value.windSpeed?.let { windSpeed ->
Wind(
@ -240,7 +241,7 @@ class ImsService @Inject constructor(
uV = hourlyResult.value.uvIndex?.toDoubleOrNull()?.let { uvi ->
UV(uvi)
},
relativeHumidity = hourlyResult.value.relativeHumidity?.toDoubleOrNull()
relativeHumidity = hourlyResult.value.relativeHumidity?.toDoubleOrNull()?.percent
)
)
}
@ -275,7 +276,7 @@ class ImsService @Inject constructor(
)
},
uV = data.analysis.uvIndex?.toDoubleOrNull()?.let { UV(it) },
relativeHumidity = data.analysis.relativeHumidity?.toDoubleOrNull(),
relativeHumidity = data.analysis.relativeHumidity?.toDoubleOrNull()?.percent,
dewPoint = data.analysis.dewPointTemp?.toDoubleOrNull()?.celsius,
dailyForecast = dailyForecast
)

View file

@ -52,6 +52,7 @@ import org.breezyweather.sources.ipma.json.IpmaAlertResult
import org.breezyweather.sources.ipma.json.IpmaDistrictResult
import org.breezyweather.sources.ipma.json.IpmaForecastResult
import org.breezyweather.sources.ipma.json.IpmaLocationResult
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -182,7 +183,7 @@ class IpmaService @Inject constructor(
TemperatureWrapper(temperature = tMax.celsius)
},
precipitationProbability = PrecipitationProbability(
total = result.probabilidadePrecipita?.toDoubleOrNull()
total = result.probabilidadePrecipita?.toDoubleOrNull()?.percent
),
wind = Wind(
degree = getWindDegree(result.ddVento)
@ -195,7 +196,7 @@ class IpmaService @Inject constructor(
?.tMin?.toDoubleOrNull() // Get next day min temperature to have overnight temp
?.let { tMin -> TemperatureWrapper(temperature = tMin.celsius) },
precipitationProbability = PrecipitationProbability(
total = result.probabilidadePrecipita?.toDoubleOrNull()
total = result.probabilidadePrecipita?.toDoubleOrNull()?.percent
),
wind = Wind(
degree = getWindDegree(result.ddVento)
@ -204,7 +205,7 @@ class IpmaService @Inject constructor(
uV = UV(
index = result.iUv?.toDoubleOrNull()
),
relativeHumidity = result.hR?.toDoubleOrNull()?.let { hr -> DailyRelativeHumidity(average = hr) }
relativeHumidity = result.hR?.toDoubleOrNull()?.let { h -> DailyRelativeHumidity(average = h.percent) }
)
}
}
@ -232,13 +233,13 @@ class IpmaService @Inject constructor(
it.probabilidadePrecipita?.toDoubleOrNull()
} else {
lastPrecipitationProbability
}
}?.percent
),
wind = Wind(
degree = getWindDegree(it.ddVento),
speed = it.ffVento?.toDoubleOrNull()?.kilometersPerHour
),
relativeHumidity = it.hR?.toDoubleOrNull()
relativeHumidity = it.hR?.toDoubleOrNull()?.percent
).also { hourly ->
if (it.probabilidadePrecipita != "-99.0") {
lastPrecipitationProbability = it.probabilidadePrecipita?.toDoubleOrNull()

View file

@ -69,6 +69,7 @@ import org.breezyweather.sources.jma.json.JmaHourlyResult
import org.breezyweather.sources.jma.json.JmaWeekAreaResult
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import org.json.JSONObject
@ -317,7 +318,7 @@ class JmaService @Inject constructor(
degree = getWindDirection(it.windDirection?.getOrNull(0)),
speed = it.wind?.getOrNull(0)?.metersPerSecond
),
relativeHumidity = it.humidity?.getOrNull(0),
relativeHumidity = it.humidity?.getOrNull(0)?.percent,
pressure = it.normalPressure?.getOrNull(0)?.hectopascals,
visibility = it.visibility?.getOrNull(0)?.meters,
dailyForecast = dailyForecast
@ -446,12 +447,18 @@ class JmaService @Inject constructor(
temperature = TemperatureWrapper(
temperature = maxTMap.getOrElse(key) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = max(
popMap.getOrElse(key + 6.hours.inWholeMilliseconds) { 0.0 },
popMap.getOrElse(key + 12.hours.inWholeMilliseconds) { 0.0 }
precipitationProbability = if (popMap.containsKey(key + 6.hours.inWholeMilliseconds) ||
popMap.containsKey(key + 12.hours.inWholeMilliseconds)
) {
PrecipitationProbability(
total = max(
popMap.getOrElse(key + 6.hours.inWholeMilliseconds) { 0.0 },
popMap.getOrElse(key + 12.hours.inWholeMilliseconds) { 0.0 }
).percent
)
)
} else {
null
}
),
night = HalfDayWrapper(
weatherText = getDailyWeatherText(
@ -466,12 +473,18 @@ class JmaService @Inject constructor(
temperature = TemperatureWrapper(
temperature = minTMap.getOrElse(key + 1.days.inWholeMilliseconds) { null }?.celsius
),
precipitationProbability = PrecipitationProbability(
total = max(
popMap.getOrElse(key + 18.hours.inWholeMilliseconds) { 0.0 },
popMap.getOrElse(key + 24.hours.inWholeMilliseconds) { 0.0 }
precipitationProbability = if (popMap.containsKey(key + 6.hours.inWholeMilliseconds) ||
popMap.containsKey(key + 12.hours.inWholeMilliseconds)
) {
PrecipitationProbability(
total = max(
popMap.getOrElse(key + 6.hours.inWholeMilliseconds) { 0.0 },
popMap.getOrElse(key + 12.hours.inWholeMilliseconds) { 0.0 }
).percent
)
)
} else {
null
}
)
)
}

View file

@ -51,6 +51,7 @@ import org.breezyweather.sources.lhmt.json.LhmtLocationsResult
import org.breezyweather.sources.lhmt.json.LhmtWeatherResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -209,9 +210,9 @@ class LhmtService @Inject constructor(
speed = it.windSpeed?.metersPerSecond,
gusts = it.windGust?.metersPerSecond
),
relativeHumidity = it.relativeHumidity,
relativeHumidity = it.relativeHumidity?.percent,
pressure = it.seaLevelPressure?.hectopascals,
cloudCover = it.cloudCover?.toInt()
cloudCover = it.cloudCover?.percent
)
}
}
@ -256,9 +257,9 @@ class LhmtService @Inject constructor(
speed = it.windSpeed?.metersPerSecond,
gusts = it.windGust?.metersPerSecond
),
relativeHumidity = it.relativeHumidity,
relativeHumidity = it.relativeHumidity?.percent,
pressure = it.seaLevelPressure?.hectopascals,
cloudCover = it.cloudCover?.toInt()
cloudCover = it.cloudCover?.percent
)
)
}

View file

@ -57,6 +57,7 @@ import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgr
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.milligramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -247,7 +248,7 @@ class LvgmcService @Inject constructor(
uV = UV(
index = it.uvIndex?.toDoubleOrNull()
),
relativeHumidity = it.relativeHumidity?.toDoubleOrNull(),
relativeHumidity = it.relativeHumidity?.toDoubleOrNull()?.percent,
pressure = it.pressure?.toDoubleOrNull()?.hectopascals,
visibility = it.visibility?.toDoubleOrNull()?.meters
)
@ -360,8 +361,8 @@ class LvgmcService @Inject constructor(
snow = it.snow?.toDoubleOrNull()?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = it.precipitationProbability?.toDoubleOrNull(),
thunderstorm = it.thunderstormProbability?.toDoubleOrNull()
total = it.precipitationProbability?.toDoubleOrNull()?.percent,
thunderstorm = it.thunderstormProbability?.toDoubleOrNull()?.percent
),
wind = Wind(
degree = it.windDirection?.toDoubleOrNull(),
@ -371,9 +372,9 @@ class LvgmcService @Inject constructor(
uV = UV(
index = it.uvIndex?.toDoubleOrNull()
),
relativeHumidity = it.relativeHumidity?.toDoubleOrNull(),
relativeHumidity = it.relativeHumidity?.toDoubleOrNull()?.percent,
pressure = it.pressure?.toDoubleOrNull()?.hectopascals,
cloudCover = it.cloudCover?.toIntOrNull()
cloudCover = it.cloudCover?.toDoubleOrNull()?.percent
)
}
}

View file

@ -47,6 +47,7 @@ import org.breezyweather.sources.meteoam.json.MeteoAmObservationResult
import org.breezyweather.sources.meteoam.json.MeteoAmReverseLocation
import org.breezyweather.sources.meteoam.json.MeteoAmReverseLocationResult
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -185,8 +186,9 @@ class MeteoAmService @Inject constructor(
currentResult.getOrElse(keys["wkmh"].toString()) { null }?.getOrElse("0") { null } as? Double
)?.kilometersPerHour
),
relativeHumidity = currentResult.getOrElse(keys["r"].toString()) { null }
?.getOrElse("0") { null } as? Double,
relativeHumidity = (
currentResult.getOrElse(keys["r"].toString()) { null }?.getOrElse("0") { null } as? Double
)?.percent,
pressure = (currentResult.getOrElse(keys["pmsl"].toString()) { null }?.getOrElse("0") { null } as? Double)
?.hectopascals
)
@ -239,7 +241,9 @@ class MeteoAmService @Inject constructor(
)?.celsius
),
precipitationProbability = PrecipitationProbability(
total = data.getOrElse(keys["tpp"].toString()) { null }?.getOrElse(i.toString()) { null } as? Double
total = (
data.getOrElse(keys["tpp"].toString()) { null }?.getOrElse(i.toString()) { null } as? Double
)?.percent
),
wind = Wind(
degree = data.getOrElse(keys["wdir"].toString()) { null }?.getOrElse(i.toString()) { null }?.let {
@ -249,8 +253,9 @@ class MeteoAmService @Inject constructor(
data.getOrElse(keys["wkmh"].toString()) { null }?.getOrElse(i.toString()) { null } as? Double
)?.kilometersPerHour
),
relativeHumidity = data.getOrElse(keys["r"].toString()) { null }
?.getOrElse(i.toString()) { null } as? Double,
relativeHumidity = (
data.getOrElse(keys["r"].toString()) { null }?.getOrElse(i.toString()) { null } as? Double
)?.percent,
pressure = (
data.getOrElse(keys["pmsl"].toString()) { null }?.getOrElse(i.toString()) { null } as? Double
)?.hectopascals

View file

@ -50,6 +50,7 @@ import org.breezyweather.sources.metie.json.MetIeWarning
import org.breezyweather.sources.metie.json.MetIeWarningResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -210,7 +211,7 @@ class MetIeService @Inject constructor(
degree = result.windDirection?.toDoubleOrNull(),
speed = result.windSpeed?.kilometersPerHour
),
relativeHumidity = result.humidity?.toDoubleOrNull(),
relativeHumidity = result.humidity?.toDoubleOrNull()?.percent,
pressure = result.pressure?.toDoubleOrNull()?.hectopascals
)
}

View file

@ -56,6 +56,7 @@ import org.breezyweather.sources.metno.json.MetNoNowcastResult
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -294,10 +295,10 @@ class MetNoService @Inject constructor(
} else {
null
},
relativeHumidity = currentTimeseries.instant?.details?.relativeHumidity,
relativeHumidity = currentTimeseries.instant?.details?.relativeHumidity?.percent,
dewPoint = currentTimeseries.instant?.details?.dewPointTemperature?.celsius,
pressure = currentTimeseries.instant?.details?.airPressureAtSeaLevel?.hectopascals,
cloudCover = currentTimeseries.instant?.details?.cloudAreaFraction?.roundToInt()
cloudCover = currentTimeseries.instant?.details?.cloudAreaFraction?.percent
)
} else {
null
@ -323,12 +324,12 @@ class MetNoService @Inject constructor(
?: hourlyForecast.data?.next12Hours?.details?.precipitationAmount?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = hourlyForecast.data?.next1Hours?.details?.probabilityOfPrecipitation
?: hourlyForecast.data?.next6Hours?.details?.probabilityOfPrecipitation
?: hourlyForecast.data?.next12Hours?.details?.probabilityOfPrecipitation,
thunderstorm = hourlyForecast.data?.next1Hours?.details?.probabilityOfThunder
?: hourlyForecast.data?.next6Hours?.details?.probabilityOfThunder
?: hourlyForecast.data?.next12Hours?.details?.probabilityOfThunder
total = hourlyForecast.data?.next1Hours?.details?.probabilityOfPrecipitation?.percent
?: hourlyForecast.data?.next6Hours?.details?.probabilityOfPrecipitation?.percent
?: hourlyForecast.data?.next12Hours?.details?.probabilityOfPrecipitation?.percent,
thunderstorm = hourlyForecast.data?.next1Hours?.details?.probabilityOfThunder?.percent
?: hourlyForecast.data?.next6Hours?.details?.probabilityOfThunder?.percent
?: hourlyForecast.data?.next12Hours?.details?.probabilityOfThunder?.percent
),
wind = hourlyForecast.data?.instant?.details?.let { details ->
Wind(
@ -337,10 +338,10 @@ class MetNoService @Inject constructor(
)
},
uV = UV(index = hourlyForecast.data?.instant?.details?.ultravioletIndexClearSky),
relativeHumidity = hourlyForecast.data?.instant?.details?.relativeHumidity,
relativeHumidity = hourlyForecast.data?.instant?.details?.relativeHumidity?.percent,
dewPoint = hourlyForecast.data?.instant?.details?.dewPointTemperature?.celsius,
pressure = hourlyForecast.data?.instant?.details?.airPressureAtSeaLevel?.hectopascals,
cloudCover = hourlyForecast.data?.instant?.details?.cloudAreaFraction?.roundToInt()
cloudCover = hourlyForecast.data?.instant?.details?.cloudAreaFraction?.percent
)
}
}

View file

@ -51,6 +51,7 @@ import org.breezyweather.sources.metoffice.json.MetOfficeHourly
import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.pascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -135,10 +136,10 @@ class MetOfficeService @Inject constructor(
feelsLike = result.dayMaxFeelsLikeTemp?.celsius
),
precipitationProbability = PrecipitationProbability(
total = result.dayProbabilityOfPrecipitation?.toDouble(),
rain = result.dayProbabilityOfRain?.toDouble(),
snow = result.dayProbabilityOfSnow?.toDouble(),
thunderstorm = result.dayProbabilityOfSferics?.toDouble()
total = result.dayProbabilityOfPrecipitation?.percent,
rain = result.dayProbabilityOfRain?.percent,
snow = result.dayProbabilityOfSnow?.percent,
thunderstorm = result.dayProbabilityOfSferics?.percent
)
),
night = HalfDayWrapper(
@ -149,10 +150,10 @@ class MetOfficeService @Inject constructor(
feelsLike = result.nightMinFeelsLikeTemp?.celsius
),
precipitationProbability = PrecipitationProbability(
total = result.nightProbabilityOfPrecipitation?.toDouble(),
rain = result.nightProbabilityOfRain?.toDouble(),
snow = result.nightProbabilityOfSnow?.toDouble(),
thunderstorm = result.nightProbabilityOfSferics?.toDouble()
total = result.nightProbabilityOfPrecipitation?.percent,
rain = result.nightProbabilityOfRain?.percent,
snow = result.nightProbabilityOfSnow?.percent,
thunderstorm = result.nightProbabilityOfSferics?.percent
)
),
uV = UV(index = result.maxUvIndex?.toDouble())
@ -184,7 +185,7 @@ class MetOfficeService @Inject constructor(
snow = result.totalSnowAmount?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = result.probOfPrecipitation?.toDouble()
total = result.probOfPrecipitation?.percent
),
wind = Wind(
degree = result.windDirectionFrom10m?.toDouble(),
@ -194,7 +195,7 @@ class MetOfficeService @Inject constructor(
uV = UV(
index = result.uvIndex?.toDouble()
),
relativeHumidity = result.screenRelativeHumidity,
relativeHumidity = result.screenRelativeHumidity?.percent,
dewPoint = result.screenDewPointTemperature?.celsius,
pressure = result.mslp?.toDouble()?.pascals,
visibility = result.visibility?.toDouble()?.meters

View file

@ -75,6 +75,7 @@ import org.breezyweather.sources.mf.json.MfWarningsOverseasResult
import org.breezyweather.sources.mf.json.MfWarningsResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -450,8 +451,8 @@ class MfService @Inject constructor(
),
uV = UV(index = dailyForecast.uvIndex?.toDouble()),
relativeHumidity = DailyRelativeHumidity(
min = dailyForecast.relativeHumidityMin?.toDouble(),
max = dailyForecast.relativeHumidityMax?.toDouble()
min = dailyForecast.relativeHumidityMin?.percent,
max = dailyForecast.relativeHumidityMax?.percent
)
)
)
@ -482,9 +483,9 @@ class MfService @Inject constructor(
speed = hourlyForecast.windSpeed?.metersPerSecond,
gusts = hourlyForecast.windSpeedGust?.metersPerSecond
),
relativeHumidity = hourlyForecast.relativeHumidity?.toDouble(),
relativeHumidity = hourlyForecast.relativeHumidity?.percent,
pressure = hourlyForecast.pSea?.hectopascals,
cloudCover = hourlyForecast.totalCloudCover
cloudCover = hourlyForecast.totalCloudCover?.percent
)
}
}
@ -561,11 +562,13 @@ class MfService @Inject constructor(
}
}
return PrecipitationProbability(
maxOf(rainProbability ?: 0.0, snowProbability ?: 0.0, iceProbability ?: 0.0),
maxOf(rainProbability ?: 0.0, snowProbability ?: 0.0, iceProbability ?: 0.0)
.takeIf { rainProbability != null || snowProbability != null || iceProbability != null }
?.percent,
null,
rainProbability,
snowProbability,
iceProbability
rainProbability?.percent,
snowProbability?.percent,
iceProbability?.percent
)
}

View file

@ -25,5 +25,6 @@ import java.util.Date
data class MfWarningOverseasComments(
@Serializable(DateSerializer::class) @SerialName("begin_time") val beginTime: Date? = null,
@Serializable(DateSerializer::class) @SerialName("end_time") val endTime: Date? = null,
// TODO: Sometimes return a single string "pas de vigilance particulière", see VIGI973 for example
@SerialName("text_bloc_item") val textBlocItems: List<MfWarningOverseasTextBlocItem>?,
)

View file

@ -54,6 +54,7 @@ import org.breezyweather.sources.mgm.json.MgmHourlyForecastResult
import org.breezyweather.sources.mgm.json.MgmLocationResult
import org.breezyweather.sources.mgm.json.MgmNormalsResult
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -249,7 +250,7 @@ class MgmService @Inject constructor(
degree = getValid(currentResult?.windDirection),
speed = getValid(currentResult?.windSpeed)?.kilometersPerHour
),
relativeHumidity = getValid(currentResult?.humidity),
relativeHumidity = getValid(currentResult?.humidity)?.percent,
pressure = getValid(currentResult?.pressure)?.hectopascals
)
}
@ -341,7 +342,7 @@ class MgmService @Inject constructor(
speed = it.windSpeed?.kilometersPerHour,
gusts = it.gust?.kilometersPerHour
),
relativeHumidity = it.humidity
relativeHumidity = it.humidity?.percent
)
)
}

View file

@ -62,6 +62,7 @@ import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgr
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.milligramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -253,7 +254,7 @@ class NamemService @Inject constructor(
degree = current?.windDir,
speed = current?.windSpeed?.metersPerSecond
),
relativeHumidity = current?.ff,
relativeHumidity = current?.ff?.percent,
pressure = current?.pslp?.hectopascals
)
}
@ -330,7 +331,7 @@ class NamemService @Inject constructor(
feelsLike = forecast.temNFeel?.celsius
),
precipitationProbability = PrecipitationProbability(
total = forecast.wwNPer
total = forecast.wwNPer?.percent
),
wind = Wind(
speed = forecast.wndN?.metersPerSecond
@ -350,7 +351,7 @@ class NamemService @Inject constructor(
feelsLike = forecast.temDFeel?.celsius
),
precipitationProbability = PrecipitationProbability(
total = forecast.wwDPer
total = forecast.wwDPer?.percent
),
wind = Wind(
speed = forecast.wndD?.metersPerSecond
@ -365,7 +366,7 @@ class NamemService @Inject constructor(
feelsLike = it.temNFeel?.celsius
),
precipitationProbability = PrecipitationProbability(
total = it.wwNPer
total = it.wwNPer?.percent
),
wind = Wind(
speed = it.wndN?.metersPerSecond
@ -394,7 +395,7 @@ class NamemService @Inject constructor(
total = it.pre?.toDoubleOrNull()?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = it.preProb?.toDoubleOrNull()
total = it.preProb?.toDoubleOrNull()?.percent
),
wind = Wind(
speed = it.wnd?.toDoubleOrNull()?.metersPerSecond

View file

@ -69,6 +69,7 @@ import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.pressure.Pressure.Companion.inchesOfMercury
import org.breezyweather.unit.pressure.Pressure.Companion.pascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.speed.Speed.Companion.milesPerHour
@ -282,7 +283,7 @@ class NwsService @Inject constructor(
} else {
null
},
relativeHumidity = it.relativeHumidity?.value,
relativeHumidity = it.relativeHumidity?.value?.percent,
dewPoint = it.dewpoint?.value?.celsius,
pressure = if (it.seaLevelPressure != null) {
it.seaLevelPressure.value?.pascals
@ -320,7 +321,7 @@ class NwsService @Inject constructor(
temperature = it.temperature?.value?.celsius
),
precipitationProbability = PrecipitationProbability(
total = it.probabilityOfPrecipitation?.value
total = it.probabilityOfPrecipitation?.value?.percent
),
wind = Wind(
degree = getWindDegree(it.windDirection),
@ -336,7 +337,7 @@ class NwsService @Inject constructor(
temperature = it.temperature?.value?.celsius
),
precipitationProbability = PrecipitationProbability(
total = it.probabilityOfPrecipitation?.value
total = it.probabilityOfPrecipitation?.value?.percent
),
wind = Wind(
degree = getWindDegree(it.windDirection),
@ -450,18 +451,18 @@ class NwsService @Inject constructor(
ice = iceAccumulationForecastList.getOrElse(it) { null }?.millimeters
),
precipitationProbability = PrecipitationProbability(
total = probabilityOfPrecipitationForecastList.getOrElse(it) { null }?.toDouble(),
thunderstorm = probabilityOfThunderForecastList.getOrElse(it) { null }?.toDouble()
total = probabilityOfPrecipitationForecastList.getOrElse(it) { null }?.percent,
thunderstorm = probabilityOfThunderForecastList.getOrElse(it) { null }?.percent
),
wind = Wind(
degree = windDirectionForecastList.getOrElse(it) { null }?.toDouble(),
speed = windSpeedForecastList.getOrElse(it) { null }?.kilometersPerHour,
gusts = windGustForecastList.getOrElse(it) { null }?.kilometersPerHour
),
relativeHumidity = relativeHumidityList.getOrElse(it) { null }?.toDouble(),
relativeHumidity = relativeHumidityList.getOrElse(it) { null }?.percent,
dewPoint = dewpointForecastList.getOrElse(it) { null }?.celsius,
pressure = pressureForecastList.getOrElse(it) { null }?.inchesOfMercury,
cloudCover = skyCoverForecastList.getOrElse(it) { null },
cloudCover = skyCoverForecastList.getOrElse(it) { null }?.percent,
visibility = visibilityForecastList.getOrElse(it) { null }?.meters
)
}

View file

@ -55,6 +55,8 @@ import org.breezyweather.unit.distance.Distance.Companion.meters
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -187,10 +189,10 @@ class OpenWeatherService @Inject constructor(
speed = currentResult.wind?.speed?.metersPerSecond,
gusts = currentResult.wind?.gust?.metersPerSecond
),
relativeHumidity = currentResult.main?.humidity?.toDouble(),
relativeHumidity = currentResult.main?.humidity?.percent,
pressure = currentResult.main?.pressure?.hectopascals,
cloudCover = currentResult.clouds?.all,
visibility = currentResult.visibility?.toDouble()?.meters
cloudCover = currentResult.clouds?.all?.percent,
visibility = currentResult.visibility?.meters
)
}
@ -234,16 +236,16 @@ class OpenWeatherService @Inject constructor(
rain = result.rain?.cumul3h?.millimeters,
snow = result.snow?.cumul3h?.millimeters
),
precipitationProbability = PrecipitationProbability(total = result.pop?.times(100.0)),
precipitationProbability = PrecipitationProbability(total = result.pop?.fraction),
wind = Wind(
degree = result.wind?.deg?.toDouble(),
speed = result.wind?.speed?.metersPerSecond,
gusts = result.wind?.gust?.metersPerSecond
),
relativeHumidity = result.main?.humidity?.toDouble(),
relativeHumidity = result.main?.humidity?.percent,
pressure = result.main?.pressure?.hectopascals,
cloudCover = result.clouds?.all,
visibility = result.visibility?.toDouble()?.meters
cloudCover = result.clouds?.all?.percent,
visibility = result.visibility?.meters
)
}
}

View file

@ -49,6 +49,7 @@ import org.breezyweather.sources.pagasa.json.PagasaHourlyResult
import org.breezyweather.sources.pagasa.json.PagasaLocationResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
@ -206,7 +207,7 @@ class PagasaService @Inject constructor(
degree = getWindDegree(it.windDirection),
speed = it.windSpeed?.substringBefore(" ")?.toDoubleOrNull()?.kilometersPerHour
),
relativeHumidity = it.humidity?.substringBefore(" ")?.toDoubleOrNull(),
relativeHumidity = it.humidity?.substringBefore(" ")?.toDoubleOrNull()?.percent,
pressure = it.pressure?.toDoubleOrNull()?.hectopascals
)
}
@ -264,7 +265,7 @@ class PagasaService @Inject constructor(
degree = it.windDirection?.attributes?.deg?.toDoubleOrNull(),
speed = it.windSpeed?.attributes?.mps?.toDoubleOrNull()?.metersPerSecond
),
relativeHumidity = it.relativeHumidity?.attributes?.value?.toDoubleOrNull()
relativeHumidity = it.relativeHumidity?.attributes?.value?.toDoubleOrNull()?.percent
)
)
}

View file

@ -55,6 +55,7 @@ import org.breezyweather.sources.smg.json.SmgWarningResult
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.milligramsPerCubicMeter
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.kilometersPerHour
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -323,7 +324,7 @@ class SmgService @Inject constructor(
index = uvResult.UV?.Custom?.getOrNull(0)?.ActualUVBReport?.getOrNull(0)?.index
?.getOrNull(0)?.Value?.getOrNull(0)?.toDoubleOrNull()
),
relativeHumidity = it.Humidity?.getOrNull(0)?.dValue?.getOrNull(0)?.toDoubleOrNull(),
relativeHumidity = it.Humidity?.getOrNull(0)?.dValue?.getOrNull(0)?.toDoubleOrNull()?.percent,
dewPoint = it.DewPoint?.getOrNull(0)?.dValue?.getOrNull(0)?.toDoubleOrNull()?.celsius,
pressure = it.MeanSeaLevelPressure?.getOrNull(0)?.dValue?.getOrNull(0)?.toDoubleOrNull()
?.hectopascals,
@ -405,7 +406,7 @@ class SmgService @Inject constructor(
degree = it.Winddiv?.getOrNull(0)?.Value?.getOrNull(0)?.toDoubleOrNull(),
speed = it.Windspd?.getOrNull(0)?.Value?.getOrNull(0)?.toDoubleOrNull()?.kilometersPerHour
),
relativeHumidity = it.Humidity?.getOrNull(0)?.Value?.getOrNull(0)?.toDoubleOrNull()
relativeHumidity = it.Humidity?.getOrNull(0)?.Value?.getOrNull(0)?.toDoubleOrNull()?.percent
)
)
}

View file

@ -43,6 +43,7 @@ import org.breezyweather.sources.smhi.json.SmhiTimeSeries
import org.breezyweather.unit.distance.Distance.Companion.kilometers
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import retrofit2.Retrofit
@ -153,14 +154,14 @@ class SmhiService @Inject constructor(
total = result.parameters.firstOrNull { it.name == "pmean" }?.values?.getOrNull(0)?.millimeters
),
precipitationProbability = PrecipitationProbability(
thunderstorm = result.parameters.firstOrNull { it.name == "tstm" }?.values?.getOrNull(0)
thunderstorm = result.parameters.firstOrNull { it.name == "tstm" }?.values?.getOrNull(0)?.percent
),
wind = Wind(
degree = result.parameters.firstOrNull { it.name == "wd" }?.values?.getOrNull(0),
speed = result.parameters.firstOrNull { it.name == "ws" }?.values?.getOrNull(0)?.metersPerSecond,
gusts = result.parameters.firstOrNull { it.name == "gust" }?.values?.getOrNull(0)?.metersPerSecond
),
relativeHumidity = result.parameters.firstOrNull { it.name == "r" }?.values?.getOrNull(0),
relativeHumidity = result.parameters.firstOrNull { it.name == "r" }?.values?.getOrNull(0)?.percent,
pressure = result.parameters.firstOrNull { it.name == "msl" }?.values?.getOrNull(0)?.hectopascals,
visibility = result.parameters.firstOrNull { it.name == "vis" }?.values?.getOrNull(0)?.kilometers
)

View file

@ -42,6 +42,7 @@ import org.breezyweather.sources.veduris.json.VedurIsStationForecast
import org.breezyweather.sources.veduris.json.VedurIsStationResult
import org.breezyweather.unit.precipitation.Precipitation.Companion.millimeters
import org.breezyweather.unit.pressure.Pressure.Companion.hectopascals
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.speed.Speed.Companion.metersPerSecond
import org.breezyweather.unit.temperature.Temperature.Companion.celsius
import org.json.JSONObject
@ -202,7 +203,7 @@ class VedurIsService @Inject constructor(
degree = it.windDirection,
speed = it.windSpeed?.metersPerSecond
),
relativeHumidity = if (it.humidity != 0.0) it.humidity else null
relativeHumidity = it.humidity.takeIf { h -> h != 0.0 }?.percent
)
)
}
@ -226,7 +227,7 @@ class VedurIsService @Inject constructor(
degree = it.windDirection,
speed = it.windSpeed?.metersPerSecond
),
relativeHumidity = if (it.humidity != 0.0) it.humidity else null
relativeHumidity = it.humidity.takeIf { h -> h != 0.0 }?.percent
)
)
}
@ -277,10 +278,10 @@ class VedurIsService @Inject constructor(
speed = it.windSpeed?.metersPerSecond,
gusts = it.maxWindGust?.metersPerSecond
),
relativeHumidity = if (it.humidity != 0.0) it.humidity else null,
relativeHumidity = it.humidity.takeIf { h -> h != 0.0 }?.percent,
dewPoint = it.dewPoint?.celsius,
pressure = it.pressure?.hectopascals,
cloudCover = it.cloudCover?.toInt()
cloudCover = it.cloudCover?.percent
)
} ?: CurrentWrapper()
}

View file

@ -30,6 +30,8 @@ import org.breezyweather.unit.precipitation.Precipitation
import org.breezyweather.unit.precipitation.Precipitation.Companion.micrometers
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.pressure.Pressure.Companion.pascals
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.ratio.Ratio.Companion.permille
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.speed.Speed.Companion.centimetersPerSecond
import org.breezyweather.unit.temperature.Temperature
@ -122,3 +124,9 @@ object DurationColumnAdapter : ColumnAdapter<Duration, Long> {
override fun encode(value: Duration): Long = value.inWholeNanoseconds
}
object RatioColumnAdapter : ColumnAdapter<Ratio, Long> {
override fun decode(databaseValue: Long): Ratio = databaseValue.permille
override fun encode(value: Ratio): Long = value.value
}

View file

@ -47,6 +47,7 @@ import org.breezyweather.unit.pollen.PollenConcentration
import org.breezyweather.unit.pollutant.PollutantConcentration
import org.breezyweather.unit.precipitation.Precipitation
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.speed.Speed
import org.breezyweather.unit.temperature.Temperature
import java.util.Date
@ -82,11 +83,11 @@ object WeatherMapper {
no2: PollutantConcentration?,
o3: PollutantConcentration?,
co: PollutantConcentration?,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
dewPoint: Temperature?,
pressure: Pressure?,
visibility: Distance?,
cloudCover: Long?,
cloudCover: Ratio?,
ceiling: Distance?,
dailyForecast: String?,
hourlyForecast: String?,
@ -130,7 +131,7 @@ object WeatherMapper {
relativeHumidity,
dewPoint,
pressure,
cloudCover?.toInt(),
cloudCover,
visibility,
ceiling,
dailyForecast,
@ -153,11 +154,11 @@ object WeatherMapper {
daytimeRainPrecipitation: Precipitation?,
daytimeSnowPrecipitation: Precipitation?,
daytimeIcePrecipitation: Precipitation?,
daytimeTotalPrecipitationProbability: Double?,
daytimeThunderstormPrecipitationProbability: Double?,
daytimeRainPrecipitationProbability: Double?,
daytimeSnowPrecipitationProbability: Double?,
daytimeIcePrecipitationProbability: Double?,
daytimeTotalPrecipitationProbability: Ratio?,
daytimeThunderstormPrecipitationProbability: Ratio?,
daytimeRainPrecipitationProbability: Ratio?,
daytimeSnowPrecipitationProbability: Ratio?,
daytimeIcePrecipitationProbability: Ratio?,
daytimeTotalPrecipitationDuration: Duration?,
daytimeThunderstormPrecipitationDuration: Duration?,
daytimeRainPrecipitationDuration: Duration?,
@ -179,11 +180,11 @@ object WeatherMapper {
nighttimeRainPrecipitation: Precipitation?,
nighttimeSnowPrecipitation: Precipitation?,
nighttimeIcePrecipitation: Precipitation?,
nighttimeTotalPrecipitationProbability: Double?,
nighttimeThunderstormPrecipitationProbability: Double?,
nighttimeRainPrecipitationProbability: Double?,
nighttimeSnowPrecipitationProbability: Double?,
nighttimeIcePrecipitationProbability: Double?,
nighttimeTotalPrecipitationProbability: Ratio?,
nighttimeThunderstormPrecipitationProbability: Ratio?,
nighttimeRainPrecipitationProbability: Ratio?,
nighttimeSnowPrecipitationProbability: Ratio?,
nighttimeIcePrecipitationProbability: Ratio?,
nighttimeTotalPrecipitationDuration: Duration?,
nighttimeThunderstormPrecipitationDuration: Duration?,
nighttimeRainPrecipitationDuration: Duration?,
@ -230,18 +231,18 @@ object WeatherMapper {
willow: PollenConcentration?,
uvIndex: Double?,
sunshineDuration: Duration?,
relativeHumidityAverage: Double?,
relativeHumidityMin: Double?,
relativeHumidityMax: Double?,
relativeHumidityAverage: Ratio?,
relativeHumidityMin: Ratio?,
relativeHumidityMax: Ratio?,
dewpointAverage: Temperature?,
dewpointMin: Temperature?,
dewpointMax: Temperature?,
pressureAverage: Pressure?,
pressureMin: Pressure?,
pressureMax: Pressure?,
cloudCoverAverage: Long?,
cloudCoverMin: Long?,
cloudCoverMax: Long?,
cloudCoverAverage: Ratio?,
cloudCoverMin: Ratio?,
cloudCoverMax: Ratio?,
visibilityAverage: Distance?,
visibilityMin: Distance?,
visibilityMax: Distance?,
@ -377,9 +378,9 @@ object WeatherMapper {
max = pressureMax
),
cloudCover = DailyCloudCover(
average = cloudCoverAverage?.toInt(),
min = cloudCoverMin?.toInt(),
max = cloudCoverMax?.toInt()
average = cloudCoverAverage,
min = cloudCoverMin,
max = cloudCoverMax
),
visibility = DailyVisibility(
average = visibilityAverage,
@ -403,11 +404,11 @@ object WeatherMapper {
rainPrecipitation: Precipitation?,
snowPrecipitation: Precipitation?,
icePrecipitation: Precipitation?,
totalPrecipitationProbability: Double?,
thunderstormPrecipitationProbability: Double?,
rainPrecipitationProbability: Double?,
snowPrecipitationProbability: Double?,
icePrecipitationProbability: Double?,
totalPrecipitationProbability: Ratio?,
thunderstormPrecipitationProbability: Ratio?,
rainPrecipitationProbability: Ratio?,
snowPrecipitationProbability: Ratio?,
icePrecipitationProbability: Ratio?,
windDegree: Double?,
windSpeed: Speed?,
windGusts: Speed?,
@ -418,10 +419,10 @@ object WeatherMapper {
o3: PollutantConcentration?,
co: PollutantConcentration?,
uvIndex: Double?,
relativeHumidity: Double?,
relativeHumidity: Ratio?,
dewPoint: Temperature?,
pressure: Pressure?,
cloudCover: Long?,
cloudCover: Ratio?,
visibility: Distance?,
): Hourly = Hourly(
Date(date),
@ -466,7 +467,7 @@ object WeatherMapper {
relativeHumidity,
dewPoint,
pressure,
cloudCover?.toInt(),
cloudCover,
visibility
)

View file

@ -165,7 +165,7 @@ class WeatherRepository(
relativeHumidity = weather.current?.relativeHumidity,
dewPoint = weather.current?.dewPoint,
pressure = weather.current?.pressure,
cloudCover = weather.current?.cloudCover?.toLong(),
cloudCover = weather.current?.cloudCover,
visibility = weather.current?.visibility,
ceiling = weather.current?.ceiling,
dailyForecast = weather.current?.dailyForecast,
@ -311,9 +311,9 @@ class WeatherRepository(
pressureMin = daily.pressure?.min,
pressureMax = daily.pressure?.max,
cloudCoverAverage = daily.cloudCover?.average?.toLong(),
cloudCoverMin = daily.cloudCover?.min?.toLong(),
cloudCoverMax = daily.cloudCover?.max?.toLong(),
cloudCoverAverage = daily.cloudCover?.average,
cloudCoverMin = daily.cloudCover?.min,
cloudCoverMax = daily.cloudCover?.max,
visibilityAverage = daily.visibility?.average,
visibilityMin = daily.visibility?.min,
@ -368,7 +368,7 @@ class WeatherRepository(
relativeHumidity = hourly.relativeHumidity,
dewPoint = hourly.dewPoint,
pressure = hourly.pressure,
cloudCover = hourly.cloudCover?.toLong(),
cloudCover = hourly.cloudCover,
visibility = hourly.visibility
)
}

View file

@ -5,6 +5,7 @@ import org.breezyweather.unit.pollen.PollenConcentration;
import org.breezyweather.unit.pollutant.PollutantConcentration;
import org.breezyweather.unit.precipitation.Precipitation;
import org.breezyweather.unit.pressure.Pressure;
import org.breezyweather.unit.ratio.Ratio;
import org.breezyweather.unit.speed.Speed;
import org.breezyweather.unit.temperature.Temperature;
@ -31,11 +32,11 @@ CREATE TABLE dailys(
daytime_snow_precipitation INTEGER AS Precipitation,
daytime_ice_precipitation INTEGER AS Precipitation,
daytime_total_precipitation_probability REAL,
daytime_thunderstorm_precipitation_probability REAL,
daytime_rain_precipitation_probability REAL,
daytime_snow_precipitation_probability REAL,
daytime_ice_precipitation_probability REAL,
daytime_total_precipitation_probability INTEGER AS Ratio,
daytime_thunderstorm_precipitation_probability INTEGER AS Ratio,
daytime_rain_precipitation_probability INTEGER AS Ratio,
daytime_snow_precipitation_probability INTEGER AS Ratio,
daytime_ice_precipitation_probability INTEGER AS Ratio,
daytime_total_precipitation_duration INTEGER AS Duration,
daytime_thunderstorm_precipitation_duration INTEGER AS Duration,
@ -64,11 +65,11 @@ CREATE TABLE dailys(
nighttime_snow_precipitation INTEGER AS Precipitation,
nighttime_ice_precipitation INTEGER AS Precipitation,
nighttime_total_precipitation_probability REAL,
nighttime_thunderstorm_precipitation_probability REAL,
nighttime_rain_precipitation_probability REAL,
nighttime_snow_precipitation_probability REAL,
nighttime_ice_precipitation_probability REAL,
nighttime_total_precipitation_probability INTEGER AS Ratio,
nighttime_thunderstorm_precipitation_probability INTEGER AS Ratio,
nighttime_rain_precipitation_probability INTEGER AS Ratio,
nighttime_snow_precipitation_probability INTEGER AS Ratio,
nighttime_ice_precipitation_probability INTEGER AS Ratio,
nighttime_total_precipitation_duration INTEGER AS Duration,
nighttime_thunderstorm_precipitation_duration INTEGER AS Duration,
@ -135,9 +136,9 @@ CREATE TABLE dailys(
sunshine_duration INTEGER AS Duration,
-- relative humidity
relative_humidity_average REAL,
relative_humidity_min REAL,
relative_humidity_max REAL,
relative_humidity_average INTEGER AS Ratio,
relative_humidity_min INTEGER AS Ratio,
relative_humidity_max INTEGER AS Ratio,
-- dewpoint
dewpoint_average INTEGER AS Temperature,
@ -150,9 +151,9 @@ CREATE TABLE dailys(
pressure_max INTEGER AS Pressure,
-- cloud cover
cloud_cover_average INTEGER,
cloud_cover_min INTEGER,
cloud_cover_max INTEGER,
cloud_cover_average INTEGER AS Ratio,
cloud_cover_min INTEGER AS Ratio,
cloud_cover_max INTEGER AS Ratio,
-- visibility
visibility_average INTEGER AS Distance,

View file

@ -4,6 +4,7 @@ import org.breezyweather.unit.distance.Distance;
import org.breezyweather.unit.pollutant.PollutantConcentration;
import org.breezyweather.unit.precipitation.Precipitation;
import org.breezyweather.unit.pressure.Pressure;
import org.breezyweather.unit.ratio.Ratio;
import org.breezyweather.unit.speed.Speed;
import org.breezyweather.unit.temperature.Temperature;
@ -29,11 +30,11 @@ CREATE TABLE hourlys(
snow_precipitation INTEGER AS Precipitation,
ice_precipitation INTEGER AS Precipitation,
total_precipitation_probability REAL,
thunderstorm_precipitation_probability REAL,
rain_precipitation_probability REAL,
snow_precipitation_probability REAL,
ice_precipitation_probability REAL,
total_precipitation_probability INTEGER AS Ratio,
thunderstorm_precipitation_probability INTEGER AS Ratio,
rain_precipitation_probability INTEGER AS Ratio,
snow_precipitation_probability INTEGER AS Ratio,
ice_precipitation_probability INTEGER AS Ratio,
wind_degree REAL,
wind_speed INTEGER AS Speed,
@ -50,10 +51,10 @@ CREATE TABLE hourlys(
uvIndex REAL,
-- details
relative_humidity REAL,
relative_humidity INTEGER AS Ratio,
dew_point INTEGER AS Temperature,
pressure INTEGER AS Pressure,
cloud_cover INTEGER,
cloud_cover INTEGER AS Ratio,
visibility INTEGER AS Distance,
UNIQUE(location_formatted_id, date) ON CONFLICT REPLACE,

View file

@ -2,6 +2,7 @@ import breezyweather.domain.weather.reference.WeatherCode;
import org.breezyweather.unit.distance.Distance;
import org.breezyweather.unit.pollutant.PollutantConcentration;
import org.breezyweather.unit.pressure.Pressure;
import org.breezyweather.unit.ratio.Ratio;
import org.breezyweather.unit.speed.Speed;
import org.breezyweather.unit.temperature.Temperature;
@ -44,11 +45,11 @@ CREATE TABLE weathers(
o3 INTEGER AS PollutantConcentration,
co INTEGER AS PollutantConcentration,
relative_humidity REAL,
relative_humidity INTEGER AS Ratio,
dew_point INTEGER AS Temperature,
pressure INTEGER AS Pressure,
visibility INTEGER AS Distance,
cloud_cover INTEGER,
cloud_cover INTEGER AS Ratio,
ceiling INTEGER AS Distance,
daily_forecast TEXT,
hourly_forecast TEXT,

View file

@ -0,0 +1,123 @@
ALTER TABLE dailys
DROP COLUMN daytime_total_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN daytime_total_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN daytime_thunderstorm_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN daytime_thunderstorm_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN daytime_rain_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN daytime_rain_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN daytime_snow_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN daytime_snow_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN daytime_ice_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN daytime_ice_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN nighttime_total_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN nighttime_total_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN nighttime_thunderstorm_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN nighttime_thunderstorm_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN nighttime_rain_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN nighttime_rain_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN nighttime_snow_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN nighttime_snow_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN nighttime_ice_precipitation_probability;
ALTER TABLE dailys
ADD COLUMN nighttime_ice_precipitation_probability INTEGER;
ALTER TABLE dailys
DROP COLUMN relative_humidity_average;
ALTER TABLE dailys
ADD COLUMN relative_humidity_average INTEGER;
ALTER TABLE dailys
DROP COLUMN relative_humidity_min;
ALTER TABLE dailys
ADD COLUMN relative_humidity_min INTEGER;
ALTER TABLE dailys
DROP COLUMN relative_humidity_max;
ALTER TABLE dailys
ADD COLUMN relative_humidity_max INTEGER;
ALTER TABLE hourlys
DROP COLUMN total_precipitation_probability;
ALTER TABLE hourlys
ADD COLUMN total_precipitation_probability INTEGER;
ALTER TABLE hourlys
DROP COLUMN thunderstorm_precipitation_probability;
ALTER TABLE hourlys
ADD COLUMN thunderstorm_precipitation_probability INTEGER;
ALTER TABLE hourlys
DROP COLUMN rain_precipitation_probability;
ALTER TABLE hourlys
ADD COLUMN rain_precipitation_probability INTEGER;
ALTER TABLE hourlys
DROP COLUMN snow_precipitation_probability;
ALTER TABLE hourlys
ADD COLUMN snow_precipitation_probability INTEGER;
ALTER TABLE hourlys
DROP COLUMN ice_precipitation_probability;
ALTER TABLE hourlys
ADD COLUMN ice_precipitation_probability INTEGER;
ALTER TABLE hourlys
DROP COLUMN relative_humidity;
ALTER TABLE hourlys
ADD COLUMN relative_humidity INTEGER;
ALTER TABLE weathers
DROP COLUMN relative_humidity;
ALTER TABLE weathers
ADD COLUMN relative_humidity INTEGER;

View file

@ -20,13 +20,11 @@ import breezyweather.domain.weather.reference.WeatherCode
import breezyweather.domain.weather.wrappers.CurrentWrapper
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
/**
* Current.
*
* default unit
* [.relativeHumidity] : [RelativeHumidityUnit.PERCENT]
*/
data class Current(
val weatherText: String? = null,
@ -35,14 +33,14 @@ data class Current(
val wind: Wind? = null,
val uV: UV? = null,
val airQuality: AirQuality? = null,
val relativeHumidity: Double? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: org.breezyweather.unit.temperature.Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Int? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
val ceiling: Distance? = null,
val dailyForecast: String? = null,

View file

@ -16,9 +16,11 @@
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
data class DailyCloudCover(
override val average: Int? = null,
override val max: Int? = null,
override val min: Int? = null,
override val average: Ratio? = null,
override val max: Ratio? = null,
override val min: Ratio? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Int>
) : DailyAvgMinMax<Ratio>

View file

@ -16,9 +16,11 @@
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
data class DailyRelativeHumidity(
override val average: Double? = null,
override val max: Double? = null,
override val min: Double? = null,
override val average: Ratio? = null,
override val max: Ratio? = null,
override val min: Ratio? = null,
override val summary: String? = null,
) : DailyAvgMinMax<Double>
) : DailyAvgMinMax<Ratio>

View file

@ -20,6 +20,7 @@ import breezyweather.domain.weather.reference.WeatherCode
import breezyweather.domain.weather.wrappers.HourlyWrapper
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
import java.util.Date
@ -37,14 +38,14 @@ data class Hourly(
val wind: Wind? = null,
val airQuality: AirQuality? = null,
val uV: UV? = null,
val relativeHumidity: Double? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: org.breezyweather.unit.temperature.Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Int? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
) : Serializable {

View file

@ -16,21 +16,20 @@
package breezyweather.domain.weather.model
import org.breezyweather.unit.ratio.Ratio
import java.io.Serializable
/**
* Precipitation duration.
*
* default unit : [ProbabilityUnit.PERCENT]
*/
data class PrecipitationProbability(
val total: Double? = null,
val thunderstorm: Double? = null,
val rain: Double? = null,
val snow: Double? = null,
val ice: Double? = null,
val total: Ratio? = null,
val thunderstorm: Ratio? = null,
val rain: Ratio? = null,
val snow: Ratio? = null,
val ice: Ratio? = null,
) : Serializable {
val isValid: Boolean
get() = total != null && total > 0
get() = total != null && total.value > 0
}

View file

@ -22,6 +22,7 @@ import breezyweather.domain.weather.model.Wind
import breezyweather.domain.weather.reference.WeatherCode
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.temperature.Temperature
/**
@ -33,14 +34,14 @@ data class CurrentWrapper(
val temperature: TemperatureWrapper? = null,
val wind: Wind? = null,
val uV: UV? = null,
val relativeHumidity: Double? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Int? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
val ceiling: Distance? = null,
val dailyForecast: String? = null,

View file

@ -25,6 +25,7 @@ import breezyweather.domain.weather.model.Wind
import breezyweather.domain.weather.reference.WeatherCode
import org.breezyweather.unit.distance.Distance
import org.breezyweather.unit.pressure.Pressure
import org.breezyweather.unit.ratio.Ratio
import org.breezyweather.unit.temperature.Temperature
import java.util.Date
import kotlin.time.Duration
@ -42,14 +43,14 @@ data class HourlyWrapper(
val precipitationProbability: PrecipitationProbability? = null,
val wind: Wind? = null,
val uV: UV? = null,
val relativeHumidity: Double? = null,
val relativeHumidity: Ratio? = null,
val dewPoint: Temperature? = null,
/**
* Pressure at sea level
* Use Kotlin extensions to initialize this value, like 1013.25.hectopascals
*/
val pressure: Pressure? = null,
val cloudCover: Int? = null,
val cloudCover: Ratio? = null,
val visibility: Distance? = null,
/**
* Duration of sunshine, NOT duration of daylight

View file

@ -1,6 +1,7 @@
# Breezy Weather unit conversion and formatting library
Android library to handle:
- Unit conversion
- Formatting in various languages, including on devices without ICU support or with missing CLDR data, with a simplified backport (no handling of plural and non-nominative rules)
@ -10,7 +11,6 @@ Some precision may be lost during conversions.
Remains to do:
- Percentage formatting
- Add missing non-English Android translations (for the units we use in Breezy Weather)
- Plus and minus operations
- Parse from string
@ -33,6 +33,12 @@ Android translations are only in English at the moment.
Supports temperature deviations conversions (such as degree days).
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Distance
@ -46,6 +52,12 @@ Android translations are only in English at the moment.
| Nautical mile | Android >= 11 | Android 7 to 10 | Android < 7 |
| Foot | Android >= 11 | Android 7 to 10 | Android < 7 |
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Speed
@ -63,6 +75,12 @@ Android translations are only in English at the moment.
¹ Not an unit, but a scale, so during conversions, uses the starting value in meters per second of the scale level
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Precipitation
@ -84,6 +102,12 @@ Android translations are only in English at the moment.
| Inch per hour | Android >= 11 | Android 8 to 10 | Android < 8 |
| Liter per square meter per hour | ❌ | ❌ | ✅ |
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Pressure
@ -96,6 +120,12 @@ Android translations are only in English at the moment.
| Millimeter of mercury | Android >= 11 | Android 7 to 10 | Android < 7 |
| Inch of mercury | Android >= 11 | Android 7 to 10 | Android < 7 |
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Air pollutant concentration
@ -106,6 +136,12 @@ Android translations are only in English at the moment.
| Microgram per cubic meter | Android >= 11 | Android 8 to 10 | Android < 8 |
| Milligram per cubic meter | Android >= 11 | Android 8 to 10 | Android < 8 |
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Pollen concentration
@ -115,6 +151,12 @@ Android translations are only in English at the moment.
|-----------------|-------------------|-----------------|----------------------|
| Per cubic meter | ❌ | ❌ | ✅ |
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Duration
@ -131,6 +173,29 @@ Android translations are only in English at the moment.
* ¹ `NumberFormatter` supports only single duration, and will not be used when needing a formatting like `1 hour and 30 minutes`.
* ² Only English translations are provided.
Supported widths for Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ✅ |
## Ratio
| Unit | `NumberFormatter`¹ | `NumberFormat` | Android translations |
|----------|--------------------|----------------|----------------------|
| Permille | Android >= 11 | ❌ | Android < 11¹ |
| Percent | Android >= 11 | Android < 11 | |
| Fraction | Android >= 11 | Android < 11 | N/A |
* ¹ Only English translations are provided.
Supported widths for `NumberFormat` and Android translations:
| Narrow | Short | Long |
|--------|-------|------|
| ❌ | ✅ | ❌ |
# License

View file

@ -169,7 +169,7 @@ value class Distance internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of distance in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `m`, `km`, `mi`, `nmi`, `ft`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -203,8 +203,6 @@ fun Long.toDistance(unit: DistanceUnit): Distance {
/**
* Returns a [Distance] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toDistance(unit: DistanceUnit): Distance {

View file

@ -61,7 +61,7 @@ value class PollenConcentration internal constructor(
* The following format is accepted:
*
* - The format of string returned by the default [PollenConcentration.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `24pcum`.
*
* @throws IllegalArgumentException if the string doesn't represent a pollen concentration in any of the supported formats.
*/
@ -78,7 +78,7 @@ value class PollenConcentration internal constructor(
* The following formats is accepted:
*
* - The format of string returned by the default [PollenConcentration.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `24pcum`.
*/
fun parseOrNull(value: String): PollenConcentration? = try {
parsePollenConcentration(value)
@ -125,7 +125,7 @@ value class PollenConcentration internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of pollen concentration in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `pcum`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -151,8 +151,6 @@ fun Long.toPollenConcentration(unit: PollenConcentrationUnit): PollenConcentrati
/**
* Returns a [PollenConcentration] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toPollenConcentration(unit: PollenConcentrationUnit): PollenConcentration {

View file

@ -1,12 +1,9 @@
package org.breezyweather.unit.pollutant
import android.content.Context
import org.breezyweather.unit.WeatherValue
import org.breezyweather.unit.formatting.UnitDecimals.Companion.formatToExactDecimals
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.microgramsPerCubicMeter
import org.breezyweather.unit.pollutant.PollutantConcentration.Companion.milligramsPerCubicMeter
import java.util.Locale
import kotlin.math.roundToLong
/*
@ -74,7 +71,7 @@ value class PollutantConcentration internal constructor(
* The following format is accepted:
*
* - The format of string returned by the default [PollutantConcentration.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `10microgpcum` or `2mgpcum`.
*
* @throws IllegalArgumentException if the string doesn't represent a pollutant concentration in any of the supported formats.
*/
@ -91,7 +88,7 @@ value class PollutantConcentration internal constructor(
* The following formats is accepted:
*
* - The format of string returned by the default [PollutantConcentration.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `10microgpcum` or `2mgpcum`.
*/
fun parseOrNull(value: String): PollutantConcentration? = try {
parsePollutantConcentration(value)
@ -138,7 +135,7 @@ value class PollutantConcentration internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of pollutant concentration in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `microgpcum`, `mgpcum`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -170,8 +167,6 @@ fun Long.toPollutantConcentration(unit: PollutantConcentrationUnit): PollutantCo
/**
* Returns a [PollutantConcentration] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toPollutantConcentration(unit: PollutantConcentrationUnit): PollutantConcentration {

View file

@ -105,7 +105,7 @@ value class Precipitation internal constructor(
* The following format is accepted:
*
* - The format of string returned by the default [Precipitation.toString] and `toString` in a specific unit,
* e.g. `50000m` or `30.5km`.
* e.g. `5mm` or `1cm`.
*
* @throws IllegalArgumentException if the string doesn't represent a precipitation in any of the supported formats.
*/
@ -122,7 +122,7 @@ value class Precipitation internal constructor(
* The following formats is accepted:
*
* - The format of string returned by the default [Precipitation.toString] and `toString` in a specific unit,
* e.g. `50000m` or `30.5km`.
* e.g. `5mm` or `1cm`.
*/
fun parseOrNull(value: String): Precipitation? = try {
parsePrecipitation(value)
@ -181,7 +181,7 @@ value class Precipitation internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of precipitation in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `microm`, `mm`, `cm`, `in`, `lpsqm`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -317,8 +317,6 @@ fun Long.toPrecipitation(unit: PrecipitationUnit): Precipitation {
/**
* Returns a [Precipitation] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toPrecipitation(unit: PrecipitationUnit): Precipitation {

View file

@ -228,8 +228,6 @@ fun Long.toPressure(unit: PressureUnit): Pressure {
/**
* Returns a [Pressure] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toPressure(unit: PressureUnit): Pressure {

View file

@ -0,0 +1,194 @@
package org.breezyweather.unit.ratio
import org.breezyweather.unit.WeatherValue
import org.breezyweather.unit.formatting.UnitDecimals.Companion.formatToExactDecimals
import org.breezyweather.unit.ratio.Ratio.Companion.fraction
import org.breezyweather.unit.ratio.Ratio.Companion.percent
import org.breezyweather.unit.ratio.Ratio.Companion.permille
import kotlin.math.roundToLong
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Represents the ratio.
*
* To construct a ratio, use either the extension function [toRatio],
* or the extension properties [permille], [percent] and [fraction],
* available on [Int], [Long], and [Double] numeric types.
*
* To get the value of this ratio expressed in a particular [ratio unit][RatioUnit]
* use the functions [toInt], [toLong], and [toDouble]
* or the properties [inPermille], [inPercent] and [inFraction].
*/
@JvmInline
value class Ratio internal constructor(
private val rawValue: Long,
) : Comparable<Ratio>, WeatherValue<RatioUnit> {
val value: Long get() = rawValue
private val storageUnit get() = RatioUnit.PERMILLE
companion object {
/** Returns a [Ratio] equal to this [Int] number of permille. */
inline val Int.permille: Ratio get() = toRatio(RatioUnit.PERMILLE)
/** Returns a [Ratio] equal to this [Long] number of permille. */
inline val Long.permille: Ratio get() = toRatio(RatioUnit.PERMILLE)
/** Returns a [Ratio] equal to this [Double] number of permille. */
inline val Double.permille: Ratio get() = toRatio(RatioUnit.PERMILLE)
/** Returns a [Ratio] equal to this [Int] number of percent. */
inline val Int.percent: Ratio get() = toRatio(RatioUnit.PERCENT)
/** Returns a [Ratio] equal to this [Long] number of percent. */
inline val Long.percent: Ratio get() = toRatio(RatioUnit.PERCENT)
/** Returns a [Ratio] equal to this [Double] number of percent. */
inline val Double.percent: Ratio get() = toRatio(RatioUnit.PERCENT)
/** Returns a [Ratio] equal to this [Int] number of fraction. */
inline val Int.fraction: Ratio get() = toRatio(RatioUnit.FRACTION)
/** Returns a [Ratio] equal to this [Long] number of fraction. */
inline val Long.fraction: Ratio get() = toRatio(RatioUnit.FRACTION)
/** Returns a [Ratio] equal to this [Double] number of percent. */
inline val Double.fraction: Ratio get() = toRatio(RatioUnit.FRACTION)
/**
* Parses a string that represents a ratio and returns the parsed [Ratio] value.
*
* The following format is accepted:
*
* - The format of string returned by the default [Ratio.toString] and `toString` in a specific unit,
* e.g. `30percent` or `542permille`.
*
* @throws IllegalArgumentException if the string doesn't represent a ratio in any of the supported formats.
*/
fun parse(value: String): Ratio = try {
parseRatio(value)
} catch (e: IllegalArgumentException) {
throw IllegalArgumentException("Invalid ratio string format: '$value'.", e)
}
/**
* Parses a string that represents a ratio and returns the parsed [Ratio] value,
* or `null` if the string doesn't represent a ratio in any of the supported formats.
*
* The following formats is accepted:
*
* - The format of string returned by the default [Ratio.toString] and `toString` in a specific unit,
* e.g. `30percent` or `542permille`.
*/
fun parseOrNull(value: String): Ratio? = try {
parseRatio(value)
} catch (e: IllegalArgumentException) {
null
}
}
override fun compareTo(other: Ratio): Int {
return this.rawValue.compareTo(other.rawValue)
}
// conversion to units
/**
* Returns the value of this ratio expressed as a [Double] number of the specified [unit].
*
* The operation may involve rounding when the result cannot be represented exactly with a [Double] number.
*/
override fun toDouble(unit: RatioUnit): Double {
return convertRatioUnit(value.toDouble(), storageUnit, unit)
}
/** The value of this ratio expressed as a [Double] number of permille. */
val inPermille: Double
get() = toDouble(RatioUnit.PERMILLE)
/** The value of this ratio expressed as a [Double] number of percent. */
val inPercent: Double
get() = toDouble(RatioUnit.PERCENT)
/** The value of this ratio expressed as a [Double] number of fraction. */
val inFraction: Double
get() = toDouble(RatioUnit.FRACTION)
/**
* Returns a string representation of this ratio value
*/
override fun toString(): String {
return toString(storageUnit)
}
/**
* Returns a string representation of this ratio value expressed in the given [unit]
* and formatted with the specified [decimals] number of digits after decimal point.
*
* @param decimals the number of digits after decimal point to show. The value must be non-negative.
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of ratio in the specified [unit] followed by that unit abbreviated name:
* `fraction`, `percent`, `permille`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
fun toString(unit: RatioUnit, decimals: Int = unit.decimals.short): String {
require(decimals >= 0) { "decimals must be not negative, but was $decimals" }
return formatToExactDecimals(toDouble(unit), decimals.coerceAtMost(unit.decimals.long)) + unit.id
}
/**
* Return null if the value is not between the provided range, otherwise this value
*/
fun toValidRangeOrNull(range: IntRange = 0..1000): Ratio? {
return takeIf { rawValue in range }
}
}
// constructing from number of units
// extension functions
/** Returns a [Ratio] equal to this [Int] number of the specified [unit]. */
fun Int.toRatio(unit: RatioUnit): Ratio {
return toLong().toRatio(unit)
}
/** Returns a [Ratio] equal to this [Long] number of the specified [unit]. */
fun Long.toRatio(unit: RatioUnit): Ratio {
return ratioOf(convertRatioUnit(this.toDouble(), unit, RatioUnit.PERMILLE).toLong())
}
/**
* Returns a [Ratio] equal to this [Double] number of the specified [unit].
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toRatio(unit: RatioUnit): Ratio {
val valueInPermille = convertRatioUnit(this, unit, RatioUnit.PERMILLE)
require(!valueInPermille.isNaN()) { "Ratio value cannot be NaN." }
return ratioOf(valueInPermille.roundToLong())
}
private fun parseRatio(value: String): Ratio {
var length = value.length
if (length == 0) throw IllegalArgumentException("The string is empty")
TODO()
}
private fun ratioOf(normalPermille: Long) = Ratio(normalPermille)

View file

@ -0,0 +1,156 @@
/*
* This file is part of Breezy Weather.
*
* Breezy Weather is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, version 3 of the License.
*
* Breezy Weather 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Breezy Weather. If not, see <https://www.gnu.org/licenses/>.
*/
package org.breezyweather.unit.ratio
import android.content.Context
import android.icu.text.NumberFormat
import android.icu.util.MeasureUnit
import android.os.Build
import org.breezyweather.unit.R
import org.breezyweather.unit.WeatherUnit
import org.breezyweather.unit.formatting.UnitDecimals
import org.breezyweather.unit.formatting.UnitTranslation
import org.breezyweather.unit.formatting.UnitWidth
import org.breezyweather.unit.formatting.format
import org.breezyweather.unit.formatting.formatWithNumberFormatter
import java.util.Locale
enum class RatioUnit(
override val id: String,
override val displayName: UnitTranslation,
override val nominative: UnitTranslation,
override val per: UnitTranslation? = null,
override val measureUnit: MeasureUnit?,
override val perMeasureUnit: MeasureUnit? = null,
val convertFromReference: (Double) -> Double,
val convertToReference: (Double) -> Double,
override val decimals: UnitDecimals,
val chartStep: Double,
) : WeatherUnit {
PERMILLE(
id = "permille",
displayName = UnitTranslation(R.string.ratio_permille_display_name_short),
nominative = UnitTranslation(R.string.ratio_permille_nominative_short),
measureUnit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) MeasureUnit.PERMILLE else null,
convertFromReference = { valueInDefaultUnit -> valueInDefaultUnit },
convertToReference = { valueInThisUnit -> valueInThisUnit },
decimals = UnitDecimals(0),
chartStep = 200.0
),
PERCENT(
id = "percent",
displayName = UnitTranslation(R.string.ratio_percent_display_name_short),
nominative = UnitTranslation(R.string.ratio_percent_nominative_short),
measureUnit = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) MeasureUnit.PERCENT else null,
convertFromReference = { valueInDefaultUnit -> valueInDefaultUnit.div(10.0) },
convertToReference = { valueInThisUnit -> valueInThisUnit.times(10.0) },
decimals = UnitDecimals(narrow = 0, short = 1, long = 1),
chartStep = 20.0
),
FRACTION(
id = "fraction",
displayName = UnitTranslation(R.string.ratio_fraction_display_name_short),
nominative = UnitTranslation(R.string.ratio_fraction_nominative_short),
measureUnit = null,
convertFromReference = { valueInDefaultUnit -> valueInDefaultUnit.div(1000.0) },
convertToReference = { valueInThisUnit -> valueInThisUnit.times(1000.0) },
decimals = UnitDecimals(narrow = 1, short = 2, long = 3),
chartStep = 0.2
),
;
/**
* @param useMeasureFormat ignored, never supported
*/
override fun format(
context: Context,
value: Number,
valueWidth: UnitWidth,
unitWidth: UnitWidth,
locale: Locale,
showSign: Boolean,
useNumberFormatter: Boolean,
useMeasureFormat: Boolean,
): String {
if (this == FRACTION) {
return value.format(
decimals = getPrecision(valueWidth),
locale = locale,
showSign = showSign,
useNumberFormatter = useNumberFormatter,
useMeasureFormat = useMeasureFormat
)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && useNumberFormatter) {
return measureUnit!!.formatWithNumberFormatter(
locale = locale,
value = value,
perUnit = null,
precision = getPrecision(valueWidth),
numberFormatterWidth = unitWidth.numberFormatterWidth!!,
showSign = showSign
)
}
if (this == PERCENT && !showSign) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
NumberFormat.getPercentInstance(locale)
.apply { maximumFractionDigits = getPrecision(valueWidth) }
.format(if (value.toDouble() > 0) value.toDouble().div(100.0) else 0)
} else {
java.text.NumberFormat.getPercentInstance(locale)
.apply { maximumFractionDigits = getPrecision(valueWidth) }
.format(if (value.toDouble() > 0) value.toDouble().div(100.0) else 0)
}
}
return formatWithAndroidTranslations(
context = context,
value = value,
valueWidth = valueWidth,
unitWidth = unitWidth,
locale = locale,
showSign = showSign,
useNumberFormatter = useNumberFormatter,
useMeasureFormat = useMeasureFormat
)
}
companion object {
fun getUnit(id: String): RatioUnit? {
return entries.firstOrNull { it.id == id }
}
}
}
/** Converts the given time ratio [value] expressed in the specified [sourceUnit] into the specified [targetUnit]. */
internal fun convertRatioUnit(
value: Double,
sourceUnit: RatioUnit,
targetUnit: RatioUnit,
): Double {
return if (sourceUnit == RatioUnit.PERMILLE) {
targetUnit.convertFromReference(value)
} else if (targetUnit == RatioUnit.PERMILLE) {
sourceUnit.convertToReference(value)
} else {
targetUnit.convertFromReference(sourceUnit.convertToReference(value))
}
}

View file

@ -112,7 +112,7 @@ value class Speed internal constructor(
* The following format is accepted:
*
* - The format of string returned by the default [Speed.toString] and `toString` in a specific unit,
* e.g. `50000m` or `30.5km`.
* e.g. `4.2mps` or `30.5kph`.
*
* @throws IllegalArgumentException if the string doesn't represent a speed in any of the supported formats.
*/
@ -129,7 +129,7 @@ value class Speed internal constructor(
* The following formats is accepted:
*
* - The format of string returned by the default [Speed.toString] and `toString` in a specific unit,
* e.g. `50000m` or `30.5km`.
* e.g. `4.2mps` or `30.5kph`.
*/
fun parseOrNull(value: String): Speed? = try {
parseSpeed(value)
@ -196,7 +196,7 @@ value class Speed internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of speed in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `cmps`, `mps`, `kph`, `mph`, `kn`, `ftps`, `bf`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -229,8 +229,6 @@ fun Long.toSpeed(unit: SpeedUnit): Speed {
/**
* Returns a [Speed] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toSpeed(unit: SpeedUnit): Speed {

View file

@ -81,7 +81,7 @@ value class Temperature internal constructor(
* The following format is accepted:
*
* - The format of string returned by the default [Temperature.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `32c` or `273.15k`.
*
* @throws IllegalArgumentException if the string doesn't represent a temperature in any of the supported formats.
*/
@ -98,7 +98,7 @@ value class Temperature internal constructor(
* The following formats is accepted:
*
* - The format of string returned by the default [Temperature.toString] and `toString` in a specific unit,
* e.g. `1013.25hpa` or `29.95inhg`.
* e.g. `32c` or `273.15k`.
*/
fun parseOrNull(value: String): Temperature? = try {
parseTemperature(value)
@ -163,7 +163,7 @@ value class Temperature internal constructor(
* No more than [unit.decimals.max] decimals will be shown, even if a larger number is requested.
*
* @return the value of temperature in the specified [unit] followed by that unit abbreviated name:
* `pa`, `hpa`, `mb`, `atm`, `mmhg`, `inhg`.
* `dc`, `c`, `f`, `k`.
*
* @throws IllegalArgumentException if [decimals] is less than zero.
*/
@ -196,8 +196,6 @@ fun Long.toTemperature(unit: TemperatureUnit): Temperature {
/**
* Returns a [Temperature] equal to this [Double] number of the specified [unit].
*
* Depending on its magnitude, the value is rounded to an integer number of nanoseconds or milliseconds.
*
* @throws IllegalArgumentException if this `Double` value is `NaN`.
*/
fun Double.toTemperature(unit: TemperatureUnit): Temperature {

View file

@ -178,7 +178,7 @@
<string name="pressure_inhg_nominative_short">%s inHg</string>
<string name="pressure_inhg_nominative_long">@string/pressure_inhg_nominative_short</string>
<!-- Duration units TODO: In other languages -->
<!-- Duration units -->
<!-- Days / Not used in Breezy Weather, so not translating -->
<string name="duration_day_display_name_short" translatable="false">day</string>
@ -230,4 +230,18 @@
<string name="duration_ns_nominative_short" translatable="false">%s ns</string>
<string name="duration_ns_nominative_long" translatable="false">@string/duration_ns_nominative_short</string>
<!-- Ratio units -->
<!-- Permille -->
<string name="ratio_permille_display_name_short" translatable="false"></string>
<string name="ratio_permille_nominative_short" translatable="false">%s ‰</string>
<!-- Percent -->
<string name="ratio_percent_display_name_short" translatable="false">%</string>
<string name="ratio_percent_nominative_short" translatable="false">%s %</string>
<!-- Fragment -->
<string name="ratio_fraction_display_name_short" translatable="false">/</string>
<string name="ratio_fraction_nominative_short" translatable="false">%s</string>
</resources>