Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ gives the timestamped jar name; a CI run started before that timestamp cannot se
the user would never see the feature otherwise). AA gets "New" in the status line only, no gone section.
On the first device test the user saw "no difference": markers need a *change*, and the history lived in an
unmarked line of a row nobody knew expands. Hence the row chevron and the green "Code history" panel.
A one-line legend entry did not explain the workflow either ("why do cleared codes show up?"): the gone header has
a "How it works" chip and the legend a link, both `showDtcHistoryHelp` (spec `doc/specs/dtc-history-help.md`). The
explanation quotes UI labels; `DtcHistoryHelpTest` fails if one is renamed in EN or PL. A chip with its own click
inside a clickable header: `addMiniBadge` returns the chip.
* **A module that did not answer is not clean** (spec `doc/specs/dtc-module-response.md`). ObdMetrics reports
`DtcModuleStatus` per scanned module via the three-argument `Lifecycle.onDTCCompleted` (the app overrides only that
one). Inside ObdMetrics, NO DATA / adapter errors never reach the DTC handler (`ObdCommandHandler` drops them), so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ internal class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragmen
row.findViewById<TextView>(R.id.dtc_legend_text).text = legendExplanation(getString(style.explanation))
table.addView(row)
}
root.findViewById<TextView>(R.id.dtc_legend_help).setOnClickListener { showDtcHistoryHelp(requireContext()) }

fun show(expanded: Boolean) {
legend.visibility = if (expanded) View.VISIBLE else View.GONE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package org.obd.graphs.preferences.dtc

import android.app.AlertDialog
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
Expand Down Expand Up @@ -90,21 +91,37 @@ internal val GONE_BADGE = BadgeStyle(R.color.philippine_green, R.drawable.dtc_hi

internal val NEW_BADGE = BadgeStyle(R.color.dtc_history_new, R.drawable.dtc_history_new, R.string.dtc_history_new_explanation)

// The "no longer reported" header's "How it works" chip: opens dtcHistoryHelp, not a state, so not in the legend table.
internal val HELP_BADGE = BadgeStyle(R.color.dialog_text_secondary, R.drawable.dtc_help, R.string.dtc_history_help_body)

// The workflow behind New / No longer reported (scan history, clear, healing, one-scan lifetime): the one-line legend
// entry was not enough to understand why cleared codes show up after a clear.
internal fun showDtcHistoryHelp(context: Context) {
AlertDialog.Builder(context)
.setTitle(R.string.dtc_history_help_title)
.setMessage(context.getText(R.string.dtc_history_help_body))
.setPositiveButton(R.string.dialog_close_window_action, null)
.show()
}

// A compact chip (icon + short label) in a section header's summary line.
internal fun LinearLayout.addMiniBadge(
label: String,
style: BadgeStyle,
description: String
) {
addView(
): TextView {
val badge =
TextView(context, null, 0, R.style.DtcMiniBadge).apply {
showBadge(label, style)
contentDescription = description
},
}
addView(
badge,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
marginStart = context.resources.getDimensionPixelSize(R.dimen.dtc_mini_badge_gap)
}
)
return badge
}

// The chips the DTC legend explains, in the order a row shows them: label and style.
Expand Down Expand Up @@ -440,6 +457,9 @@ internal class DiagnosticTroubleCodeViewAdapter internal constructor(
name.setTextColor(ContextCompat.getColor(context, R.color.philippine_green))
summary.removeAllViews()
summary.addMiniBadge(header.codeCount.toString(), GONE_BADGE, name.text.toString())
// Its own click: the header itself expands / collapses the section.
summary.addMiniBadge(context.getString(R.string.dtc_history_help_chip), HELP_BADGE, context.getString(R.string.dtc_history_help_title))
.setOnClickListener { showDtcHistoryHelp(it.context) }
arrow.rotation = if (header.expanded) 180f else 0f
itemView.contentDescription =
"${context.getString(R.string.dtc_history_gone_header, header.codeCount)}. " +
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/dtc_help.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- "How it works" chip on the "no longer reported" header: a question mark in a circle (chip size). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z" />
</vector>
16 changes: 16 additions & 0 deletions app/src/main/res/layout/dialog_dtc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@
android:text="@string/dtc.legend.gone"
android:textColor="?android:attr/textColorPrimary"
android:textSize="13sp" />

<!-- Opens the workflow explanation (showDtcHistoryHelp), as the "How it works" chip on the section header does. -->
<TextView
android:id="@+id/dtc_legend_help"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:background="?attr/selectableItemBackground"
android:drawablePadding="6dp"
android:gravity="center_vertical"
android:minHeight="48dp"
android:text="@string/dtc.history.help.link"
android:textColor="?android:attr/textColorLink"
android:textSize="13sp"
app:drawableStartCompat="@drawable/dtc_help"
app:drawableTint="?android:attr/textColorLink" />
</LinearLayout>

<TextView
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-pl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,10 @@
<string name="dtc.share.modules_no_response">Moduły bez odpowiedzi: %1$s</string>
<string name="dtc.history.gone_title">Już nie zgłaszane</string>
<string name="dtc.history.last_reported">Ostatnio zgłoszony %1$s</string>
<string name="dtc.history.help.chip">Jak to działa</string>
<string name="dtc.history.help.link">Jak działają oznaczenia Nowy i Już nie zgłaszane?</string>
<string name="dtc.history.help.title">Jak działa historia kodów</string>
<string name="dtc.history.help.body">Każdy odczyt jest zapisywany w telefonie, osobno dla każdego pojazdu (wg VIN). Lista porównuje ostatni odczyt z wcześniejszymi.\n\n<b>Nowy</b>: kod pojawił się w tym pojeździe po raz pierwszy.\n\n<b>Już nie zgłaszane</b>: poprzedni odczyt zawierał kod, ostatni już nie. Dzieje się tak po:\n\u2022 Kasowaniu: odczyt wykonywany po kasowaniu nie znajduje kodów, więc każdy skasowany kod trafia tutaj.\n\u2022 Samoistnym ustąpieniu: samochód sam usuwa zapisany kod po kilku przejazdach bez usterki.\n\n<b>Przykład</b>: odczyt znajduje P0100 i P0200. Naciskasz Skasuj kody. Oba kody są teraz w sekcji Już nie zgłaszane, z datą ostatniego zgłoszenia.\n\nSekcja istnieje przez jeden odczyt. Kolejny odczyt jest porównywany z odczytem po kasowaniu, w którym nie było żadnego z tych kodów, więc sekcja znika. Kody zostają w historii.\n\nKod, który wróci, wraca na listę bez oznaczenia Nowy: pojazd zgłaszał go już wcześniej.\n\nModuły, które nie odpowiedziały lub zostały odznaczone, są pomijane: kod nigdy nie jest oznaczony jako zniknięty, bo jego modułu nie zapytano.</string>
<string name="dtc.share.state">Stan: %1$s</string>
<string name="dtc.share.snapshot">Migawka (rekord %1$d):</string>
<string name="dtc.share.unknown_did">Nieznany DID</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1016,6 +1016,10 @@
<string name="dtc.share.modules_no_response">Modules without an answer: %1$s</string>
<string name="dtc.history.gone_title">No longer reported</string>
<string name="dtc.history.last_reported">Last reported %1$s</string>
<string name="dtc.history.help.chip">How it works</string>
<string name="dtc.history.help.link">How do New and No longer reported work?</string>
<string name="dtc.history.help.title">How the code history works</string>
<string name="dtc.history.help.body">Every scan is saved on the phone, separately for each vehicle (by VIN). The list compares the latest scan with the earlier ones.\n\n<b>New</b>: the code appears on this vehicle for the first time.\n\n<b>No longer reported</b>: the previous scan had the code, the latest one does not. This happens after:\n\u2022 Clear: the read that follows a clear finds no codes, so every cleared code moves here.\n\u2022 Healing: the car drops a stored code by itself after enough drives without the fault.\n\n<b>Example</b>: a scan finds P0100 and P0200. You press Clear Codes. Both now show under No longer reported, with the date they were last reported.\n\nThe section lasts one scan. The next scan is compared with the scan after the clear, which had neither code, so the section empties. The codes stay in the history.\n\nA code that comes back returns to the list without the New label: the vehicle reported it before.\n\nModules that did not answer, or that you unchecked, are left out: a code is never marked as gone because its module was not asked.</string>
<string name="dtc.share.state">State: %1$s</string>
<string name="dtc.share.snapshot">Snapshot (Record %1$d):</string>
<string name="dtc.share.unknown_did">Unknown DID</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2019-2026, Tomasz Żebrowski
*
* <p>Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* <p>http://www.apache.org/licenses/LICENSE-2.0
*
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.obd.graphs.preferences.dtc

import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
import javax.xml.parsers.DocumentBuilderFactory

// The workflow explanation names the labels and the button the dialog shows: a renamed label must not leave the
// explanation talking about one that no longer exists, in either language.
class DtcHistoryHelpTest {
private fun strings(folder: String): Map<String, String> {
val document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(File("src/main/res/$folder/strings.xml"))
val nodes = document.getElementsByTagName("string")
return (0 until nodes.length).associate { i ->
val node = nodes.item(i)
node.attributes.getNamedItem("name").nodeValue to node.textContent
}
}

@Test
fun `the explanation names the labels and the clear button as the dialog shows them`() {
listOf("values", "values-pl").forEach { folder ->
val strings = strings(folder)
val help = strings.getValue("dtc.history.help.body")
listOf("dtc.history.new", "dtc.history.gone_title", "dtc.action_clear_codes").forEach { key ->
assertTrue("$folder: help does not mention $key \"${strings[key]}\"", help.contains(strings.getValue(key)))
}
listOf("dtc.history.help.chip", "dtc.history.help.link", "dtc.history.help.title").forEach { key ->
assertTrue("$folder: $key missing", strings[key]?.isNotBlank() == true)
}
}
}
}
57 changes: 57 additions & 0 deletions doc/specs/dtc-history-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Spec: explain the code-history workflow in the DTC list

2026-10-15 · branch `feat/dtc-history-help` (ObdGraphs only) · Status: implemented, on-device check pending

Follow-up to the per-vehicle history (`dtc-history.md`).

## Summary

**Problem.** After Clear Codes, a "No longer reported" section full of the cleared codes appears. The only
explanation was one sentence at the bottom of the collapsed label legend ("codes the previous scan of their module had
and this one has not"). The user could not tell why the section appears, what it compares, or why it is gone after the
next scan.

**Scope.** `:app` only (DTC dialog, adapter, strings EN + PL).

## Behaviour changes

| Area | Before | After |
|---|---|---|
| "No longer reported" header | Title, green check chip with the count, arrow | Also a grey "How it works" chip (question-mark icon). Tapping the chip opens the explanation; tapping the rest of the header still expands / collapses |
| Label legend | Chip table + the one-line "No longer reported" sentence | Also a link "How do New and No longer reported work?" opening the same explanation |
| Explanation dialog | — | "How the code history works": scans saved per vehicle; New; No longer reported after a clear or after healing; worked example (P0100, P0200, Clear Codes); the section lasts one scan; a returning code is not New; unanswered / unchecked modules are left out |

## Settings / keys touched

None. New strings: `dtc.history.help.chip`, `.link`, `.title`, `.body` (EN + PL).

## Implementation

* `DiagnosticTroubleCodeViewAdapter.kt`: `HELP_BADGE`, `showDtcHistoryHelp(context)` (an `AlertDialog`, body from
`getText` so its `<b>` spans stay). `addMiniBadge` now returns the chip, so a caller can give it its own click;
`GoneHeaderViewHolder` does that for the help chip.
* `HELP_BADGE` is not a row state, so it is not in `DTC_LEGEND`.
* `dialog_dtc.xml`: `dtc_legend_help` under the legend's gone sentence; wired in `attachLegend`.

## Backward compatibility

UI only; nothing stored.

## Tests

* `DtcHistoryHelpTest` (done): in EN and PL the explanation contains the current "New", "No longer reported" and
"Clear Codes" labels, and the chip / link / title strings exist. Checked to fail when the example names a button
label that does not exist.
* `DtcLegendTest` unchanged: the help chip is not a legend entry.
* [ ] Phone: after a clear, the header shows the chip; the chip opens the dialog without toggling the section; the
header still toggles; the legend link opens the dialog; PL header fits at phone width (title + count + chip + arrow).

## Risks

* PL "Już nie zgłaszane" + count + "Jak to działa" may crowd the header on a narrow phone; the title wraps first
(weight 1), the chips keep their size.

## Out of scope

* Showing the cleared codes' freeze frames from the gone rows (history keeps them; gone rows have no details).
* An AA explanation (AA has no gone section).
Loading