Legg til TalkBack-støtte og nordindikator på kompasset
Tilgjengelegheit (Android + PWA): - Semantiske landemerke (header, main, aside, role=dialog) - aria-live-regionar for statusoppdateringar og lasteoverlegg - Fokusindikatorar (:focus-visible) og prefers-reduced-motion - Auka trykkmål til 48dp (infoknapp, oppdater, del, widget) - contentDescription på kart, kompass og framdriftsindikator - aria-current og role=listitem på tilfluktsromliste - Fokusfangst og fokusgjenoppretting i lasteoverlegg - Ikkje-farge-indikator (▶) for valt tilfluktsrom - Dynamisk lang-attributt basert på oppdaga språk - Lokaliserte aria-label (en/nb/nn) Nordindikator: - DirectionArrowView teiknar diskret «N»-markør på omkrinsen - Roterer uavhengig av hovudpila for kompasskalibrering - Berre på stor kompassvisning, ikkje minipila Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f9f8ac3d60
commit
6ba35add2f
17 changed files with 240 additions and 36 deletions
|
|
@ -522,10 +522,11 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
|||
R.string.direction_arrow_description, distanceText
|
||||
)
|
||||
|
||||
// Update compass view
|
||||
// Update compass view (large arrow gets a north indicator)
|
||||
binding.compassDistanceText.text = distanceText
|
||||
binding.compassAddressText.text = selected.shelter.adresse
|
||||
binding.directionArrow.setDirection(arrowAngle)
|
||||
binding.directionArrow.setNorthAngle(-deviceHeading)
|
||||
binding.directionArrow.contentDescription = getString(
|
||||
R.string.direction_arrow_description, distanceText
|
||||
)
|
||||
|
|
@ -840,6 +841,7 @@ class MainActivity : AppCompatActivity(), SensorEventListener {
|
|||
val arrowAngle = bearing - deviceHeading
|
||||
|
||||
binding.directionArrow.setDirection(arrowAngle)
|
||||
binding.directionArrow.setNorthAngle(-deviceHeading)
|
||||
binding.miniArrow.setDirection(arrowAngle)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import no.naiv.tilfluktsrom.R
|
|||
* rotationAngle = shelterBearing - deviceHeading
|
||||
* This gives the direction the user needs to walk, adjusted for which
|
||||
* way they're currently facing.
|
||||
*
|
||||
* Optionally draws a discrete north indicator on the perimeter so users
|
||||
* can validate compass calibration against a known direction.
|
||||
*/
|
||||
class DirectionArrowView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
|
|
@ -25,6 +28,7 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
) : View(context, attrs, defStyleAttr) {
|
||||
|
||||
private var rotationAngle = 0f
|
||||
private var northAngle = Float.NaN
|
||||
|
||||
private val arrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = context.getColor(R.color.shelter_primary)
|
||||
|
|
@ -37,7 +41,18 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
strokeWidth = 4f
|
||||
}
|
||||
|
||||
private val northPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = 0x99CFD8DC.toInt() // text_secondary at ~60% opacity
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
|
||||
private val northTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = 0x99CFD8DC.toInt()
|
||||
textAlign = Paint.Align.CENTER
|
||||
}
|
||||
|
||||
private val arrowPath = Path()
|
||||
private val northPath = Path()
|
||||
|
||||
/**
|
||||
* Set the rotation angle in degrees.
|
||||
|
|
@ -48,6 +63,16 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the angle to north in the view's coordinate space.
|
||||
* This is typically -deviceHeading (where north is on screen).
|
||||
* Set to Float.NaN to hide the north indicator.
|
||||
*/
|
||||
fun setNorthAngle(degrees: Float) {
|
||||
northAngle = degrees
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
|
||||
|
|
@ -55,6 +80,11 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
val cy = height / 2f
|
||||
val size = minOf(width, height) * 0.4f
|
||||
|
||||
// Draw north indicator first (behind the main arrow)
|
||||
if (!northAngle.isNaN()) {
|
||||
drawNorthIndicator(canvas, cx, cy, size)
|
||||
}
|
||||
|
||||
canvas.save()
|
||||
canvas.rotate(rotationAngle, cx, cy)
|
||||
|
||||
|
|
@ -74,4 +104,32 @@ class DirectionArrowView @JvmOverloads constructor(
|
|||
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a small north indicator: a tiny triangle and "N" label
|
||||
* placed on the perimeter of the view, pointing inward toward center.
|
||||
*/
|
||||
private fun drawNorthIndicator(canvas: Canvas, cx: Float, cy: Float, arrowSize: Float) {
|
||||
val radius = arrowSize * 1.35f
|
||||
val tickSize = arrowSize * 0.1f
|
||||
|
||||
// Scale "N" text relative to the view
|
||||
northTextPaint.textSize = arrowSize * 0.18f
|
||||
|
||||
canvas.save()
|
||||
canvas.rotate(northAngle, cx, cy)
|
||||
|
||||
// Small triangle at the top of the perimeter circle
|
||||
northPath.reset()
|
||||
northPath.moveTo(cx, cy - radius)
|
||||
northPath.lineTo(cx - tickSize, cy - radius - tickSize * 1.8f)
|
||||
northPath.lineTo(cx + tickSize, cy - radius - tickSize * 1.8f)
|
||||
northPath.close()
|
||||
canvas.drawPath(northPath, northPaint)
|
||||
|
||||
// "N" label just outside the triangle
|
||||
canvas.drawText("N", cx, cy - radius - tickSize * 2.2f, northTextPaint)
|
||||
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,14 +31,15 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:accessibilityLiveRegion="polite"
|
||||
android:textColor="@color/status_text"
|
||||
android:textSize="12sp"
|
||||
tools:text="@string/status_ready" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/infoButton"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_civil_defense_info"
|
||||
android:src="@drawable/ic_info"
|
||||
|
|
@ -46,8 +47,8 @@
|
|||
|
||||
<ImageButton
|
||||
android:id="@+id/refreshButton"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_refresh"
|
||||
android:src="@drawable/ic_refresh"
|
||||
|
|
@ -71,6 +72,7 @@
|
|||
android:id="@+id/mapView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/a11y_map"
|
||||
app:layout_constraintTop_toBottomOf="@id/statusBar"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottomSheet" />
|
||||
|
||||
|
|
@ -80,6 +82,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/compass_bg"
|
||||
android:contentDescription="@string/a11y_compass"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/statusBar"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottomSheet">
|
||||
|
|
@ -223,8 +226,8 @@
|
|||
|
||||
<ImageButton
|
||||
android:id="@+id/shareButton"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/action_share"
|
||||
|
|
@ -249,6 +252,8 @@
|
|||
android:background="@color/loading_bg"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:importantForAccessibility="yes"
|
||||
android:accessibilityLiveRegion="assertive"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
|
|
@ -262,6 +267,7 @@
|
|||
android:id="@+id/loadingProgress"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:contentDescription="@string/status_loading"
|
||||
android:indeterminate="true" />
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
|
|
@ -65,9 +65,10 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/widgetRefreshButton"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:padding="8dp"
|
||||
android:contentDescription="@string/action_refresh"
|
||||
android:src="@drawable/ic_refresh" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>
|
||||
<string name="content_desc_shelter_item">%1$s, %2$s, %3$d plasser</string>
|
||||
<string name="compass_accuracy_warning">Upresist kompass - %s</string>
|
||||
<string name="a11y_map">Tilfluktsromkart</string>
|
||||
<string name="a11y_compass">Kompassnavigasjon</string>
|
||||
|
||||
<!-- Sivilforsvar -->
|
||||
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
<string name="direction_arrow_description">Retning til tilfluktsrom, %s unna</string>
|
||||
<string name="content_desc_shelter_item">%1$s, %2$s, %3$d plassar</string>
|
||||
<string name="compass_accuracy_warning">Upresis kompass - %s</string>
|
||||
<string name="a11y_map">Tilfluktsromkart</string>
|
||||
<string name="a11y_compass">Kompassnavigasjon</string>
|
||||
|
||||
<!-- Sivilforsvar -->
|
||||
<string name="action_civil_defense_info">Sivilforsvarsinformasjon</string>
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@
|
|||
<string name="direction_arrow_description">Direction to shelter, %s away</string>
|
||||
<string name="content_desc_shelter_item">%1$s, %2$s, %3$d places</string>
|
||||
<string name="compass_accuracy_warning">Low accuracy - %s</string>
|
||||
<string name="a11y_map">Shelter map</string>
|
||||
<string name="a11y_compass">Compass navigation</string>
|
||||
|
||||
<!-- Civil defense info -->
|
||||
<string name="action_civil_defense_info">Civil defense information</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue