Utbedre WCAG 2.2 AA-avvik og legg til tilgjengelighetserklæring
Fire WCAG-avvik utbedret før appen kan lisensieres til offentlig sektor (WAD / EN 301 549): - SC 1.4.3: Advarselsbanner til #BF360C (~5,5:1 mot hvit, var 3,75:1). - SC 2.4.7: defaultFocusHighlightEnabled i temaet; ikonknapper bruker bundet selectableItemBackground. - SC 4.1.2: DirectionArrowView rapporterer ImageView-rolle og annonserer retning + avstand til TalkBack når en 45°-sektor krysses, med 750 ms-struping mot spam. - SC 2.3.3: Snapper til nærmeste 45° når brukeren har slått av animasjoner (ANIMATOR_DURATION_SCALE=0). Nye retningsstrenger i nb/nn/en. JVM-unittester for sektoraritmetikken sikrer grensetilfeller (negative vinkler, 360°-overgang, vilkårlige vinkler). ACCESSIBILITY.md dokumenterer tilgjengelighetserklæring etter WAD artikkel 7, i bokmål med engelsk preambel og vedlikeholdsjekkliste. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
22fad9e1db
commit
e4f44ede97
11 changed files with 364 additions and 16 deletions
|
|
@ -109,4 +109,7 @@ dependencies {
|
|||
|
||||
// Coroutines
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")
|
||||
|
||||
// JVM unit tests
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,11 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
|||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
// Only the full-screen compass arrow speaks direction changes to
|
||||
// TalkBack; the mini arrow in the bottom sheet would otherwise
|
||||
// double-announce every turn of the device.
|
||||
binding.miniArrow.announceDirectionChanges = false
|
||||
|
||||
repository = ShelterRepository(this)
|
||||
locationProvider = LocationProvider(this)
|
||||
mapCacheManager = MapCacheManager(this)
|
||||
|
|
@ -516,22 +521,20 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
|||
R.string.shelter_capacity, selected.shelter.plasser
|
||||
) + " - " + distanceText
|
||||
|
||||
// Update direction arrows with accessibility descriptions
|
||||
// Update direction arrows. The view derives its own accessibility
|
||||
// description from distanceText + the angle we feed into
|
||||
// setDirection(), so we don't set contentDescription manually.
|
||||
val bearing = selected.bearingDegrees.toFloat()
|
||||
val arrowAngle = bearing - deviceHeading
|
||||
binding.miniArrow.setDistanceText(distanceText)
|
||||
binding.miniArrow.setDirection(arrowAngle)
|
||||
binding.miniArrow.contentDescription = getString(
|
||||
R.string.direction_arrow_description, distanceText
|
||||
)
|
||||
|
||||
// Update compass view (large arrow gets a north indicator)
|
||||
binding.compassDistanceText.text = distanceText
|
||||
binding.compassAddressText.text = selected.shelter.adresse
|
||||
binding.directionArrow.setDistanceText(distanceText)
|
||||
binding.directionArrow.setDirection(arrowAngle)
|
||||
binding.directionArrow.setNorthAngle(-deviceHeading)
|
||||
binding.directionArrow.contentDescription = getString(
|
||||
R.string.direction_arrow_description, distanceText
|
||||
)
|
||||
|
||||
// Emphasize the selected marker on the map
|
||||
highlightSelectedMarker(selected.shelter.lokalId)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ import android.graphics.Canvas
|
|||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.os.SystemClock
|
||||
import android.provider.Settings
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import no.naiv.tilfluktsrom.R
|
||||
|
||||
/**
|
||||
|
|
@ -20,6 +23,19 @@ import no.naiv.tilfluktsrom.R
|
|||
*
|
||||
* Optionally draws a discrete north indicator on the perimeter so users
|
||||
* can validate compass calibration against a known direction.
|
||||
*
|
||||
* Accessibility:
|
||||
* - Exposes direction ("straight ahead" / "to the right" / ...) and
|
||||
* distance to TalkBack via contentDescription, updated on every call
|
||||
* to [setDirection] using the distance set via [setDistanceText].
|
||||
* - Announces changes when the direction crosses a 45° sector boundary,
|
||||
* throttled so rapid device rotation doesn't spam the screen reader.
|
||||
*
|
||||
* Reduced motion (WCAG 2.3.3):
|
||||
* - When the user has set Android's animator duration scale to 0
|
||||
* ("Remove animations"), the arrow snaps to the nearest 45° sector
|
||||
* instead of rotating smoothly, removing the continuous motion that
|
||||
* can trigger vestibular symptoms.
|
||||
*/
|
||||
class DirectionArrowView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
|
@ -30,6 +46,26 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
private var rotationAngle = 0f
|
||||
private var northAngle = Float.NaN
|
||||
|
||||
/** Last distance text set by the owner (e.g. "1.2 km"). */
|
||||
private var distanceText: String = ""
|
||||
|
||||
/** Controls whether this instance announces direction changes to
|
||||
* TalkBack. The mini arrow in the bottom sheet sets this to false
|
||||
* so the full-screen compass arrow is the only one that speaks. */
|
||||
var announceDirectionChanges: Boolean = true
|
||||
|
||||
private var lastAnnouncedSector: Int = -1
|
||||
private var lastAnnounceTimeMs: Long = 0L
|
||||
|
||||
/** True when the user has disabled OS animations. Read once; users
|
||||
* rarely change this setting while the view is alive. */
|
||||
private val reduceMotion: Boolean =
|
||||
Settings.Global.getFloat(
|
||||
context.contentResolver,
|
||||
Settings.Global.ANIMATOR_DURATION_SCALE,
|
||||
1f
|
||||
) == 0f
|
||||
|
||||
private val arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(R.color.shelter_primary)
|
||||
style = Paint.Style.FILL
|
||||
|
|
@ -54,13 +90,33 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
private val arrowPath = Path()
|
||||
private val northPath = Path()
|
||||
|
||||
/**
|
||||
* Set the distance text used in the accessibility description
|
||||
* (e.g. "1.2 km"). Should be called whenever the selected shelter
|
||||
* or user location changes. Does not trigger a redraw on its own;
|
||||
* the next [setDirection] call picks up the new text.
|
||||
*/
|
||||
fun setDistanceText(text: String) {
|
||||
distanceText = text
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rotation angle in degrees.
|
||||
* 0 = pointing up (north/forward), positive = clockwise.
|
||||
* 0 = pointing up (toward the shelter if facing it), positive = clockwise.
|
||||
*
|
||||
* Also refreshes the accessibility description and, if the sector
|
||||
* changed, announces the new direction to TalkBack.
|
||||
*/
|
||||
fun setDirection(degrees: Float) {
|
||||
rotationAngle = degrees
|
||||
invalidate()
|
||||
// Visual: snap to 8 cardinal sectors when reduce-motion is on so
|
||||
// the arrow doesn't rotate continuously with device movement.
|
||||
val displayAngle = if (reduceMotion) snapToSector(degrees) else degrees
|
||||
if (displayAngle != rotationAngle) {
|
||||
rotationAngle = displayAngle
|
||||
invalidate()
|
||||
}
|
||||
|
||||
updateAccessibility(degrees)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,4 +188,71 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
|
||||
super.onInitializeAccessibilityNodeInfo(info)
|
||||
// Present as an ImageView so screen readers treat the description
|
||||
// as the full semantic content, not a container label.
|
||||
info.className = "android.widget.ImageView"
|
||||
}
|
||||
|
||||
/**
|
||||
* Update [contentDescription] from the latest bearing and distance,
|
||||
* and announce the change if the user has crossed a 45° sector
|
||||
* boundary and enough time has passed since the last announcement.
|
||||
*/
|
||||
private fun updateAccessibility(angleDegrees: Float) {
|
||||
val sector = sectorIndex(angleDegrees)
|
||||
val directionText = context.getString(sectorStringRes(sector))
|
||||
val description = context.getString(
|
||||
R.string.a11y_direction_with_distance,
|
||||
directionText,
|
||||
distanceText
|
||||
)
|
||||
contentDescription = description
|
||||
|
||||
if (!announceDirectionChanges) return
|
||||
|
||||
val now = SystemClock.uptimeMillis()
|
||||
if (sector != lastAnnouncedSector &&
|
||||
now - lastAnnounceTimeMs >= ANNOUNCE_INTERVAL_MS
|
||||
) {
|
||||
lastAnnouncedSector = sector
|
||||
lastAnnounceTimeMs = now
|
||||
announceForAccessibility(description)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sectorStringRes(sector: Int): Int = when (sector) {
|
||||
0 -> R.string.a11y_dir_forward
|
||||
1 -> R.string.a11y_dir_forward_right
|
||||
2 -> R.string.a11y_dir_right
|
||||
3 -> R.string.a11y_dir_back_right
|
||||
4 -> R.string.a11y_dir_back
|
||||
5 -> R.string.a11y_dir_back_left
|
||||
6 -> R.string.a11y_dir_left
|
||||
7 -> R.string.a11y_dir_forward_left
|
||||
else -> R.string.a11y_dir_forward
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Minimum gap between TalkBack announcements. Prevents a rapidly
|
||||
* turning user from flooding the screen reader queue. */
|
||||
private const val ANNOUNCE_INTERVAL_MS = 750L
|
||||
|
||||
/** Map a raw angle (any float) to one of 8 sectors, 0..7 starting
|
||||
* at "forward" (0°) and going clockwise in 45° steps. Pure function;
|
||||
* exposed as `internal` so it can be unit-tested on the JVM without
|
||||
* the Android framework. */
|
||||
internal fun sectorIndex(angleDegrees: Float): Int {
|
||||
var a = angleDegrees % 360f
|
||||
if (a < 0f) a += 360f
|
||||
return (((a + 22.5f) / 45f).toInt()) % 8
|
||||
}
|
||||
|
||||
/** Snap to the centre of the sector the current angle falls in.
|
||||
* Returns a value in {0, 45, 90, 135, 180, 225, 270, 315}. */
|
||||
internal fun snapToSector(angleDegrees: Float): Float =
|
||||
sectorIndex(angleDegrees) * 45f
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,11 +36,14 @@
|
|||
android:textSize="12sp"
|
||||
tools:text="@string/status_ready" />
|
||||
|
||||
<!-- Use bounded selectableItemBackground (not borderless) so the
|
||||
focus ring is clearly visible when navigating by keyboard or
|
||||
switch control — required for WCAG 2.4.7 Focus Visible. -->
|
||||
<ImageButton
|
||||
android:id="@+id/infoButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/action_civil_defense_info"
|
||||
android:src="@drawable/ic_info"
|
||||
app:tint="@color/status_text" />
|
||||
|
|
@ -49,7 +52,7 @@
|
|||
android:id="@+id/refreshButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/action_refresh"
|
||||
android:src="@drawable/ic_refresh"
|
||||
app:tint="@color/status_text" />
|
||||
|
|
@ -90,7 +93,9 @@
|
|||
<no.naiv.tilfluktsrom.ui.DirectionArrowView
|
||||
android:id="@+id/directionArrow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:importantForAccessibility="yes" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/compassDistanceText"
|
||||
|
|
@ -197,7 +202,8 @@
|
|||
<no.naiv.tilfluktsrom.ui.DirectionArrowView
|
||||
android:id="@+id/miniArrow"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp" />
|
||||
android:layout_height="48dp"
|
||||
android:importantForAccessibility="yes" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
|
|
@ -229,7 +235,7 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/action_share"
|
||||
android:src="@drawable/ic_share"
|
||||
app:tint="@color/text_secondary" />
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@
|
|||
<string name="compass_accuracy_warning">Upresist kompass - %s</string>
|
||||
<string name="a11y_map">Tilfluktsromkart</string>
|
||||
<string name="a11y_compass">Kompassnavigasjon</string>
|
||||
<string name="a11y_direction_with_distance">Retning til tilfluktsrom: %1$s, %2$s unna</string>
|
||||
<string name="a11y_dir_forward">rett frem</string>
|
||||
<string name="a11y_dir_forward_right">fremover til høyre</string>
|
||||
<string name="a11y_dir_right">til høyre</string>
|
||||
<string name="a11y_dir_back_right">bak til høyre</string>
|
||||
<string name="a11y_dir_back">rett bak</string>
|
||||
<string name="a11y_dir_back_left">bak til venstre</string>
|
||||
<string name="a11y_dir_left">til venstre</string>
|
||||
<string name="a11y_dir_forward_left">fremover til venstre</string>
|
||||
|
||||
<!-- Sivilforsvar -->
|
||||
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,15 @@
|
|||
<string name="compass_accuracy_warning">Upresis kompass - %s</string>
|
||||
<string name="a11y_map">Tilfluktsromkart</string>
|
||||
<string name="a11y_compass">Kompassnavigasjon</string>
|
||||
<string name="a11y_direction_with_distance">Retning til tilfluktsrom: %1$s, %2$s unna</string>
|
||||
<string name="a11y_dir_forward">rett fram</string>
|
||||
<string name="a11y_dir_forward_right">framover til høgre</string>
|
||||
<string name="a11y_dir_right">til høgre</string>
|
||||
<string name="a11y_dir_back_right">bak til høgre</string>
|
||||
<string name="a11y_dir_back">rett bak</string>
|
||||
<string name="a11y_dir_back_left">bak til venstre</string>
|
||||
<string name="a11y_dir_left">til venstre</string>
|
||||
<string name="a11y_dir_forward_left">framover til venstre</string>
|
||||
|
||||
<!-- Sivilforsvar -->
|
||||
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
<color name="text_primary">#ECEFF1</color>
|
||||
<color name="text_secondary">#90A4AE</color>
|
||||
|
||||
<color name="warning_bg">#E65100</color>
|
||||
<!-- Warning banner: darker orange so white text meets WCAG 2.2 AA (SC 1.4.3).
|
||||
#BF360C gives ~5.5:1 vs white; previous #E65100 was only 3.75:1. -->
|
||||
<color name="warning_bg">#BF360C</color>
|
||||
<color name="warning_text">#FFFFFF</color>
|
||||
|
||||
<color name="white">#FFFFFF</color>
|
||||
|
|
|
|||
|
|
@ -69,6 +69,16 @@
|
|||
<string name="compass_accuracy_warning">Low accuracy - %s</string>
|
||||
<string name="a11y_map">Shelter map</string>
|
||||
<string name="a11y_compass">Compass navigation</string>
|
||||
<!-- %1$s is one of the a11y_dir_* strings, %2$s is the distance. -->
|
||||
<string name="a11y_direction_with_distance">Direction to shelter: %1$s, %2$s away</string>
|
||||
<string name="a11y_dir_forward">straight ahead</string>
|
||||
<string name="a11y_dir_forward_right">ahead to the right</string>
|
||||
<string name="a11y_dir_right">to the right</string>
|
||||
<string name="a11y_dir_back_right">behind to the right</string>
|
||||
<string name="a11y_dir_back">behind you</string>
|
||||
<string name="a11y_dir_back_left">behind to the left</string>
|
||||
<string name="a11y_dir_left">to the left</string>
|
||||
<string name="a11y_dir_forward_left">ahead to the left</string>
|
||||
|
||||
<!-- Civil defense info -->
|
||||
<string name="action_civil_defense_info">Civil defense information</string>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
<item name="android:windowBackground">@color/background</item>
|
||||
<item name="android:statusBarColor">@color/status_bar_bg</item>
|
||||
<item name="android:navigationBarColor">@color/background</item>
|
||||
<!-- WCAG 2.4.7 Focus Visible: ensure the framework draws a default
|
||||
focus highlight on any focusable view that doesn't supply one of
|
||||
its own (e.g. our custom DirectionArrowView). -->
|
||||
<item name="android:defaultFocusHighlightEnabled">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Tilfluktsrom.Dialog" parent="Theme.Material3.Dark.Dialog">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package no.naiv.tilfluktsrom.ui
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Pure-JVM tests for [DirectionArrowView.sectorIndex] and
|
||||
* [DirectionArrowView.snapToSector].
|
||||
*
|
||||
* These two functions are what the reduce-motion (WCAG 2.3.3) remediation
|
||||
* relies on: when animations are disabled, the arrow snaps to one of eight
|
||||
* 45° sectors instead of rotating continuously. The sector index also drives
|
||||
* the TalkBack accessibility description (WCAG 4.1.2), so any off-by-one in
|
||||
* this arithmetic is a silent a11y regression. Worth locking down with tests
|
||||
* that can run in milliseconds without booting an emulator.
|
||||
*/
|
||||
class DirectionArrowViewTest {
|
||||
|
||||
@Test
|
||||
fun sectorIndex_wholeCardinals_returnExpectedSector() {
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(0f)) // forward
|
||||
assertEquals(1, DirectionArrowView.sectorIndex(45f)) // forward-right
|
||||
assertEquals(2, DirectionArrowView.sectorIndex(90f)) // right
|
||||
assertEquals(3, DirectionArrowView.sectorIndex(135f)) // back-right
|
||||
assertEquals(4, DirectionArrowView.sectorIndex(180f)) // back
|
||||
assertEquals(5, DirectionArrowView.sectorIndex(225f)) // back-left
|
||||
assertEquals(6, DirectionArrowView.sectorIndex(270f)) // left
|
||||
assertEquals(7, DirectionArrowView.sectorIndex(315f)) // forward-left
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sectorIndex_boundariesSnapToNextSector() {
|
||||
// A sector covers [center - 22.5°, center + 22.5°). The boundary
|
||||
// value itself should belong to the following sector.
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(22.4f))
|
||||
assertEquals(1, DirectionArrowView.sectorIndex(22.5f))
|
||||
assertEquals(1, DirectionArrowView.sectorIndex(44.9f))
|
||||
assertEquals(1, DirectionArrowView.sectorIndex(67.4f))
|
||||
assertEquals(2, DirectionArrowView.sectorIndex(67.5f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sectorIndex_wrapsAroundAt360() {
|
||||
// Anything within 22.5° of north should land back in sector 0.
|
||||
assertEquals(7, DirectionArrowView.sectorIndex(337.4f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(337.5f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(359.99f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(360f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(720f))
|
||||
assertEquals(2, DirectionArrowView.sectorIndex(450f)) // 450 % 360 = 90
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sectorIndex_handlesNegativeAngles() {
|
||||
// Sensors can produce angles slightly below zero due to smoothing;
|
||||
// subtracting deviceHeading from shelterBearing routinely yields
|
||||
// negative values. The function must normalise rather than throw.
|
||||
assertEquals(7, DirectionArrowView.sectorIndex(-45f))
|
||||
assertEquals(4, DirectionArrowView.sectorIndex(-180f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(-10f))
|
||||
assertEquals(6, DirectionArrowView.sectorIndex(-90f))
|
||||
assertEquals(0, DirectionArrowView.sectorIndex(-360f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snapToSector_alwaysReturnsMultipleOf45InZeroTo315() {
|
||||
// Sweep a dense range of inputs; every output must be a whole 45°
|
||||
// step in [0, 315]. This is the invariant that prevents vestibular
|
||||
// motion triggers when reduce-motion is enabled.
|
||||
var angle = -720f
|
||||
while (angle <= 720f) {
|
||||
val snapped = DirectionArrowView.snapToSector(angle)
|
||||
val remainder = snapped % 45f
|
||||
assertEquals(
|
||||
"snap($angle) = $snapped is not a multiple of 45°",
|
||||
0f, remainder, 0.0001f
|
||||
)
|
||||
assertEquals(
|
||||
"snap($angle) = $snapped is outside [0, 315]",
|
||||
true, snapped in 0f..315f
|
||||
)
|
||||
angle += 0.37f // irrational-ish step hits boundaries from both sides
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun snapToSector_knownPoints() {
|
||||
assertEquals(0f, DirectionArrowView.snapToSector(0f), 0.0001f)
|
||||
assertEquals(0f, DirectionArrowView.snapToSector(22.4f), 0.0001f)
|
||||
assertEquals(45f, DirectionArrowView.snapToSector(22.5f), 0.0001f)
|
||||
assertEquals(90f, DirectionArrowView.snapToSector(90f), 0.0001f)
|
||||
assertEquals(180f, DirectionArrowView.snapToSector(180f), 0.0001f)
|
||||
// -45° normalises to 315° (sector 7), which snaps to 315°.
|
||||
assertEquals(315f, DirectionArrowView.snapToSector(-45f), 0.0001f)
|
||||
// Just past full rotation wraps to forward.
|
||||
assertEquals(0f, DirectionArrowView.snapToSector(359.9f), 0.0001f)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue