Skip to content

Commit

Permalink
get primary measure from android locale apis
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv committed Jan 8, 2025
1 parent baaf76f commit bb0bb7d
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
package dev.sargunv.maplibrecompose.material3

import android.icu.util.LocaleData
import android.icu.util.ULocale
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure

internal actual fun scaleBarMeasurePreference(): ScaleBarMeasure? = null
@Composable
internal actual fun systemDefaultPrimaryMeasure(): ScaleBarMeasure? {
if (android.os.Build.VERSION.SDK_INT < 28) return null
val locales = LocalContext.current.resources.configuration.locales
if (locales.isEmpty) return null
return when (LocaleData.getMeasurementSystem(ULocale.forLocale(locales[0]))) {
LocaleData.MeasurementSystem.SI -> ScaleBarMeasure.Metric
LocaleData.MeasurementSystem.US -> ScaleBarMeasure.FeetAndMiles
LocaleData.MeasurementSystem.UK -> ScaleBarMeasure.YardsAndMiles
else -> null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import dev.sargunv.maplibrecompose.material3.defaultScaleBarMeasures
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
Expand Down Expand Up @@ -44,7 +45,7 @@ public fun DisappearingScaleBar(
metersPerDp: Double,
zoom: Double,
modifier: Modifier = Modifier,
measures: ScaleBarMeasures? = null,
measures: ScaleBarMeasures = defaultScaleBarMeasures(),
haloColor: Color = MaterialTheme.colorScheme.surface,
color: Color = contentColorFor(haloColor),
textStyle: TextStyle = MaterialTheme.typography.labelMedium,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import dev.sargunv.maplibrecompose.material3.drawPathsWithHalo
import dev.sargunv.maplibrecompose.material3.drawTextWithHalo

/** Which measures to show on the scale bar. */
public data class ScaleBarMeasures(val first: ScaleBarMeasure, val second: ScaleBarMeasure? = null)
public data class ScaleBarMeasures(
val primary: ScaleBarMeasure,
val secondary: ScaleBarMeasure? = null,
)

/**
* A scale bar composable that shows the current scale of the map in feet, meters or feet and meters
Expand All @@ -47,14 +50,12 @@ public data class ScaleBarMeasures(val first: ScaleBarMeasure, val second: Scale
public fun ScaleBar(
metersPerDp: Double,
modifier: Modifier = Modifier,
measures: ScaleBarMeasures? = null,
measures: ScaleBarMeasures = defaultScaleBarMeasures(),
haloColor: Color = MaterialTheme.colorScheme.surface,
color: Color = contentColorFor(haloColor),
textStyle: TextStyle = MaterialTheme.typography.labelSmall,
alignment: Alignment.Horizontal = Alignment.Start,
) {
@Suppress("NAME_SHADOWING") val measures = measures ?: defaultScaleBarMeasures()

val textMeasurer = rememberTextMeasurer()
// longest possible text
val maxTextSizePx =
Expand All @@ -75,15 +76,15 @@ public fun ScaleBar(

val fullStrokeWidth = haloStrokeWidth * 2 + strokeWidth

val textCount = if (measures.second != null) 2 else 1
val textCount = if (measures.secondary != null) 2 else 1
val totalHeight = (maxTextSize.height + textVerticalPadding) * textCount + fullStrokeWidth

BoxWithConstraints(modifier.size(totalMaxWidth, totalHeight)) {
// scale bar start/end should not overlap horizontally with canvas bounds
val maxBarLength = maxWidth - fullStrokeWidth

val params1 = scaleBarParameters(measures.first, metersPerDp, maxBarLength)
val params2 = measures.second?.let { scaleBarParameters(it, metersPerDp, maxBarLength) }
val params1 = scaleBarParameters(measures.primary, metersPerDp, maxBarLength)
val params2 = measures.secondary?.let { scaleBarParameters(it, metersPerDp, maxBarLength) }

Canvas(modifier.fillMaxSize()) {
val fullStrokeWidthPx = fullStrokeWidth.toPx()
Expand Down Expand Up @@ -185,9 +186,9 @@ private fun scaleBarParameters(
metersPerDp: Double,
maxBarLength: Dp,
): ScaleBarParams {
val max = maxBarLength.value * metersPerDp / measure.unitToMeter
val max = maxBarLength.value * metersPerDp / measure.unitInMeters
val stop = findStop(max, measure.stops)
return ScaleBarParams((stop * measure.unitToMeter / metersPerDp).dp, measure.getText(stop))
return ScaleBarParams((stop * measure.unitInMeters / metersPerDp).dp, measure.getText(stop))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.jetbrains.compose.resources.stringResource
/** A measure to show in the scale bar */
public interface ScaleBarMeasure {
/** one unit of this measure in meters */
public val unitToMeter: Double
public val unitInMeters: Double

/** List of stops, sorted ascending, at which the scalebar should show */
public val stops: List<Double>
Expand All @@ -22,7 +22,7 @@ public interface ScaleBarMeasure {

/** A measure of meters and kilometers */
public data object Metric : ScaleBarMeasure {
override val unitToMeter: Double = 1.0
override val unitInMeters: Double = 1.0

override val stops: List<Double> = buildStops(mantissas = listOf(1, 2, 5), exponents = -1..7)

Expand All @@ -40,7 +40,7 @@ public interface ScaleBarMeasure {

private const val FEET_IN_MILE: Int = 5280

override val unitToMeter: Double = 0.3048
override val unitInMeters: Double = 0.3048

override val stops: List<Double> =
listOf(
Expand All @@ -63,7 +63,7 @@ public interface ScaleBarMeasure {

private const val YARDS_IN_MILE: Int = 1760

override val unitToMeter: Double = 0.9144
override val unitInMeters: Double = 0.9144

override val stops: List<Double> =
listOf(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,70 +1,91 @@
package dev.sargunv.maplibrecompose.material3

import androidx.compose.runtime.Composable
import androidx.compose.ui.text.intl.Locale
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure.*
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure.FeetAndMiles
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure.Metric
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure.YardsAndMiles
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasures

/** user system preference for the scale bar measure, if any */
internal expect fun scaleBarMeasurePreference(): ScaleBarMeasure?
/** use system locale APIs for the primary scale bar measure */
@Composable internal expect fun systemDefaultPrimaryMeasure(): ScaleBarMeasure?

/**
* default scale bar measures to use, depending on the user's locale (or system preferences, if
* available)
*/
internal fun defaultScaleBarMeasures(): ScaleBarMeasures =
scaleBarMeasurePreference()?.let { ScaleBarMeasures(it) }
?: defaultScaleBarMeasures(Locale.current.region)

/** default scale bar measure to use, depending on the locale */
internal fun defaultScaleBarMeasures(country: String?): ScaleBarMeasures {
if (!country.isNullOrEmpty()) {
return when (country) {
// United states and its unincorporated territories
"US" -> ScaleBarMeasures(FeetAndMiles, Metric)
"AS",
"GU",
"MP",
"PR",
"VI" -> ScaleBarMeasures(FeetAndMiles, Metric)

// former United states territories / Compact of Free Association
"FM",
"MH",
"PW" -> ScaleBarMeasures(Metric, FeetAndMiles)
/** if the system APIs don't provide a primary measure, fall back to our hardcoded lists */
internal fun fallbackDefaultPrimaryMeasure(region: String?): ScaleBarMeasure =
when (region) {
in regionsUsingFeetAndMiles -> FeetAndMiles
in regionsUsingYardsAndMiles -> YardsAndMiles
else -> Metric
}

// United kingdom with its overseas territories and crown dependencies
"GB" -> ScaleBarMeasures(YardsAndMiles, Metric)
"AI",
"BM",
"FK",
"GG",
"GI",
"GS",
"IM",
"IO",
"JE",
"KY",
"MS",
"PN",
"SH",
"TC",
"VG" -> ScaleBarMeasures(YardsAndMiles, Metric)
/** countries using non-metric units will see both systems by default */
internal fun defaultSecondaryMeasure(primary: ScaleBarMeasure, region: String?): ScaleBarMeasure? =
when (primary) {
FeetAndMiles -> Metric
YardsAndMiles -> Metric
Metric ->
when (region) {
in regionsUsingFeetAndMiles -> FeetAndMiles
in regionsUsingYardsAndMiles -> YardsAndMiles
else -> null
}
else -> null // should never happen because the primary is always one of the above
}

// former British overseas territories / colonies
"BS",
"BZ",
"GD",
"KN",
"VC" -> ScaleBarMeasures(Metric, YardsAndMiles)
internal val regionsUsingFeetAndMiles =
listOf(
// United states and its unincorporated territories
"US",
"AS",
"GU",
"MP",
"PR",
"VI",
// former United states territories / Compact of Free Association
"FM",
"MH",
"PW",
// Liberia
"LR",
)

// Myanmar
"MM" -> ScaleBarMeasures(Metric, YardsAndMiles)
// Liberia
"LR" -> ScaleBarMeasures(Metric, FeetAndMiles)
internal val regionsUsingYardsAndMiles =
listOf(
// United kingdom with its overseas territories and crown dependencies
"GB",
"AI",
"BM",
"FK",
"GG",
"GI",
"GS",
"IM",
"IO",
"JE",
"KY",
"MS",
"PN",
"SH",
"TC",
"VG",
// former British overseas territories / colonies
"BS",
"BZ",
"GD",
"KN",
"VC",
// Myanmar
"MM",
)

else -> ScaleBarMeasures(Metric)
}
}
return ScaleBarMeasures(Metric)
/**
* default scale bar measures to use, depending on the user's locale (or system preferences, if
* available)
*/
@Composable
internal fun defaultScaleBarMeasures(): ScaleBarMeasures {
val region = Locale.current.region
val primary = systemDefaultPrimaryMeasure() ?: fallbackDefaultPrimaryMeasure(region)
return ScaleBarMeasures(primary = primary, secondary = defaultSecondaryMeasure(primary, region))
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.sargunv.maplibrecompose.material3

import androidx.compose.runtime.Composable
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure

internal actual fun scaleBarMeasurePreference(): ScaleBarMeasure? = null
@Composable internal actual fun systemDefaultPrimaryMeasure(): ScaleBarMeasure? = null
// TODO on macOS, there should be an API for this
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package dev.sargunv.maplibrecompose.material3

import androidx.compose.runtime.Composable
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure

internal actual fun scaleBarMeasurePreference(): ScaleBarMeasure? = null
@Composable internal actual fun systemDefaultPrimaryMeasure(): ScaleBarMeasure? = null

/*
TODO iOS/Mac developer:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package dev.sargunv.maplibrecompose.material3

import androidx.compose.runtime.Composable
import dev.sargunv.maplibrecompose.material3.controls.ScaleBarMeasure

internal actual fun scaleBarMeasurePreference(): ScaleBarMeasure? = null
@Composable internal actual fun systemDefaultPrimaryMeasure(): ScaleBarMeasure? = null

0 comments on commit bb0bb7d

Please sign in to comment.