From b0287e6357badfc67346a6d7166ec50a1f1afa82 Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sat, 7 Mar 2026 11:41:27 +0100 Subject: [PATCH 01/14] feat: improve dtc handling --- .../java/org/obd/graphs/DataLoggerWifiTest.kt | 6 ++-- ...sticTroubleCodePreferenceDialogFragment.kt | 26 +++++++++++++---- .../dtc/DiagnosticTroubleCodeViewAdapter.kt | 11 ++++---- .../datalogger/VehicleCapabilitiesManager.kt | 28 +++++++++++++++---- gradle.properties | 2 +- 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/app/src/androidTest/java/org/obd/graphs/DataLoggerWifiTest.kt b/app/src/androidTest/java/org/obd/graphs/DataLoggerWifiTest.kt index 7f13b21ea..9354a3576 100644 --- a/app/src/androidTest/java/org/obd/graphs/DataLoggerWifiTest.kt +++ b/app/src/androidTest/java/org/obd/graphs/DataLoggerWifiTest.kt @@ -65,9 +65,9 @@ class DataLoggerWifiTest { tcpTestRunner( assert = { - assertTrue(vehicleCapabilitiesManager.getDTC().contains("26E400")) - assertTrue(vehicleCapabilitiesManager.getDTC().contains("D00800")) - assertTrue(vehicleCapabilitiesManager.getDTC().contains("2BC100")) + assertTrue(vehicleCapabilitiesManager.getDiagnosticTroubleCodes().contains("26E400")) + assertTrue(vehicleCapabilitiesManager.getDiagnosticTroubleCodes().contains("D00800")) + assertTrue(vehicleCapabilitiesManager.getDiagnosticTroubleCodes().contains("2BC100")) }, arrange = { // arrange diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt index b4f4d4fa5..01f628614 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt @@ -25,9 +25,13 @@ import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R import org.obd.graphs.bl.datalogger.vehicleCapabilitiesManager import org.obd.graphs.preferences.CoreDialogFragment +import org.obd.metrics.api.model.DiagnosticTroubleCode +import org.obd.metrics.command.dtc.DtcComponent -class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragment() { - override fun onCreateView( + class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragment() { + + + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, @@ -35,10 +39,22 @@ class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragment() { requestWindowFeatures() val root = inflater.inflate(R.layout.dialog_dtc, container, false) - val dtc = vehicleCapabilitiesManager.getDTC() - + val dtc = vehicleCapabilitiesManager.getDiagnosticTroubleCodes() if (dtc.isEmpty()) { - dtc.add(resources.getString(R.string.pref_dtc_no_dtc_found)) + val noDTC = DiagnosticTroubleCode( + "", + "", + null, + resources.getString(R.string.pref_dtc_no_dtc_found), + 0, + null, + null, + null, + null, + DtcComponent("","") + ) + + dtc.add(noDTC) } val adapter = DiagnosticTroubleCodeViewAdapter(context, dtc) diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt index 6828b428b..fcecbc77f 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt @@ -27,10 +27,11 @@ import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R import org.obd.graphs.ui.common.COLOR_CARDINAL import org.obd.graphs.ui.common.setText +import org.obd.metrics.api.model.DiagnosticTroubleCode -class DiagnosticTroubleCodeViewAdapter internal constructor( - context: Context?, - private var data: MutableCollection, + class DiagnosticTroubleCodeViewAdapter internal constructor( + context: Context?, + private var data: MutableCollection, ) : RecyclerView.Adapter() { private val mInflater: LayoutInflater = LayoutInflater.from(context) @@ -44,9 +45,9 @@ class DiagnosticTroubleCodeViewAdapter internal constructor( position: Int, ) { data.elementAt(position).run { - holder.code.setText(this, COLOR_CARDINAL, Typeface.NORMAL, 1f) + holder.code.setText("${this.standardCode}-${this.failureType.code}", COLOR_CARDINAL, Typeface.NORMAL, 1f) + holder.description.setText(this.description, Color.GRAY, Typeface.NORMAL, 1f) } - holder.description.setText("", Color.GRAY, Typeface.NORMAL, 1f) } override fun getItemCount(): Int = data.size diff --git a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt index 3da309b07..dffe21ea0 100644 --- a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt +++ b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt @@ -18,10 +18,13 @@ package org.obd.graphs.bl.datalogger import android.util.Log import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.core.type.TypeReference +import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.readValue import org.obd.graphs.preferences.Prefs +import org.obd.metrics.api.model.DiagnosticTroubleCode import org.obd.metrics.api.model.VehicleCapabilities class VehicleMetadata(var name: String, var value: String) @@ -36,6 +39,8 @@ class VehicleCapabilitiesManager { registerModule(KotlinModule()) configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) + configure(JsonParser.Feature.IGNORE_UNDEFINED, true) + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) } internal fun updateCapabilities(vehicleCapabilities: VehicleCapabilities) { @@ -57,8 +62,8 @@ class VehicleCapabilitiesManager { } putString(PREF_VEHICLE_METADATA, mapper.writeValueAsString(vehicleCapabilities.metadata)) - putStringSet(PREF_DTC, vehicleCapabilities.dtc.map { it.code }.toHashSet()) - apply() + putString(PREF_DTC, mapper.writeValueAsString(vehicleCapabilities.dtc)) + commit() } } @@ -68,14 +73,25 @@ class VehicleCapabilitiesManager { .sortedWith(compareBy { t -> pidList.firstOrNull { a -> a.pid == t.uppercase() } }).toMutableList() } - fun getDTC(): MutableList { - return Prefs.getStringSet(PREF_DTC, emptySet())!!.toMutableList() - } + fun getDiagnosticTroubleCodes(): MutableList = + try { + var preferences = Prefs.getString(PREF_DTC, "")!! + Log.e("DTC","$preferences") + preferences = preferences + .removeSurrounding("\"") + .replace("\\\"", "\"") + + val typeRef = object : TypeReference>() {} + mapper.readValue(preferences, typeRef).toMutableList() + } catch (e: Throwable){ + Log.e(LOG_TAG, "Failed to read Diagnostic Trouble Code from preferences",e) + mutableListOf() + } fun getVehicleCapabilities(): MutableList { val preferences = Prefs.getString(PREF_VEHICLE_METADATA, "")!! - try { + return if (preferences.isEmpty()) mutableListOf() else { val map: Map = mapper.readValue(preferences) return map.map { (k, v) -> VehicleMetadata(k, v) }.toMutableList() diff --git a/gradle.properties b/gradle.properties index 0f73a4988..910fabe7f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ android.useAndroidX=true android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -obdMetricVersion=11.14.0-SNAPSHOT +obdMetricVersion=11.15.0-SNAPSHOT From 7d414deb5d1c01d41b045b020f268b93794f6f78 Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sat, 7 Mar 2026 21:10:04 +0100 Subject: [PATCH 02/14] feat: improve DTC serialization --- .../bl/datalogger/VehicleCapabilitiesManager.kt | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt index dffe21ea0..81b340993 100644 --- a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt +++ b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt @@ -62,7 +62,7 @@ class VehicleCapabilitiesManager { } putString(PREF_VEHICLE_METADATA, mapper.writeValueAsString(vehicleCapabilities.metadata)) - putString(PREF_DTC, mapper.writeValueAsString(vehicleCapabilities.dtc)) + putString(PREF_DTC, mapper.writeValueAsString(vehicleCapabilities.dtc)) commit() } } @@ -76,13 +76,10 @@ class VehicleCapabilitiesManager { fun getDiagnosticTroubleCodes(): MutableList = try { var preferences = Prefs.getString(PREF_DTC, "")!! - Log.e("DTC","$preferences") - preferences = preferences - .removeSurrounding("\"") - .replace("\\\"", "\"") - - val typeRef = object : TypeReference>() {} - mapper.readValue(preferences, typeRef).toMutableList() + if (preferences.startsWith("\"") && preferences.endsWith("\"")) { + preferences = mapper.readValue(preferences) + } + mapper.readValue>(preferences).toMutableList() } catch (e: Throwable){ Log.e(LOG_TAG, "Failed to read Diagnostic Trouble Code from preferences",e) mutableListOf() From 8967efb45c15f6bcce0e8e01903b85bb274738b5 Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sat, 7 Mar 2026 21:31:56 +0100 Subject: [PATCH 03/14] feat: improve preferences section --- app/src/main/res/drawable/ic_action_execute.xml | 10 ++++++++++ app/src/main/res/values/strings.xml | 14 +++++++------- app/src/main/res/xml/preferences.xml | 3 ++- 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 app/src/main/res/drawable/ic_action_execute.xml diff --git a/app/src/main/res/drawable/ic_action_execute.xml b/app/src/main/res/drawable/ic_action_execute.xml new file mode 100644 index 000000000..94099fc6d --- /dev/null +++ b/app/src/main/res/drawable/ic_action_execute.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86a4766b1..7394a4682 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -129,14 +129,13 @@ DTC are available.Please check DTC section Enable DTC readings Enable DTC notifications - Perform DTC cleanup after the session starts + Sends an alert when new Diagnostic Trouble Codes (DTCs) are found. - When enabled, you will receive notification whenever DTC becomes available. + Enable DTC cleanup + Automatically clears Diagnostic Trouble Codes (DTCs) from the ECU memory. - When enabled, this option will automatically erase Diagnostic Trouble Codes (DTCs) from the ECU memory. - - When enabled, Diagnostic Trouble Codes (DTCs) will be read during the ECU session. + Read Diagnostic Trouble Codes (DTCs) while connected to the ECU. When enabled, vehicle metadata will be retrieved during the ECU session. Enabling this option will allow supported OBD2 PIDs/Sensors to be read during the session with the ECU. @@ -285,7 +284,7 @@ Dynamic selector enabled When enabled, colors are applied according to the selected dynamic mode: SPORT, NORMAL, or ECO. - Diagnostic Trouble Code + Diagnostic Trouble Codes Code Description No Diagnosis Trouble Code (DTC) found. @@ -526,7 +525,8 @@ In this section, you can view vehicle metadata fetched from the ECU, including VIN, ECU type, hardware serial number, etc. This section contains preferences related to Vehicle Profiles. You can change the vehicle profile here. Power-related settings are available in this section, allowing you to define the application’s behavior when the charger is plugged in or removed. - Diagnostic Trouble Codes (DTCs) retrieved from the ECU are displayed in this section. + Tap to view active Diagnostic Trouble Codes (DTCs) retrieved from the ECU. + View Current DTCs Stable diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6691dbf2d..66b21ec85 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -559,10 +559,11 @@ app:singleLineTitle="false"> Date: Sat, 7 Mar 2026 21:45:05 +0100 Subject: [PATCH 04/14] feat: Naming refactoring --- ...sticTroubleCodePreferenceDialogFragment.kt | 4 +- ...VehicleMetadataPreferenceDialogFragment.kt | 4 +- .../PidDefinitionPreferenceDialogFragment.kt | 4 +- .../datalogger/VehicleCapabilitiesManager.kt | 84 +++++++++++-------- .../bl/datalogger/WorkflowOrchestrator.kt | 2 +- 5 files changed, 54 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt index 01f628614..e4cf28df5 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R -import org.obd.graphs.bl.datalogger.vehicleCapabilitiesManager +import org.obd.graphs.bl.datalogger.VehicleCapabilitiesManager import org.obd.graphs.preferences.CoreDialogFragment import org.obd.metrics.api.model.DiagnosticTroubleCode import org.obd.metrics.command.dtc.DtcComponent @@ -39,7 +39,7 @@ import org.obd.metrics.command.dtc.DtcComponent requestWindowFeatures() val root = inflater.inflate(R.layout.dialog_dtc, container, false) - val dtc = vehicleCapabilitiesManager.getDiagnosticTroubleCodes() + val dtc = VehicleCapabilitiesManager.getDiagnosticTroubleCodes() if (dtc.isEmpty()) { val noDTC = DiagnosticTroubleCode( "", diff --git a/app/src/main/java/org/obd/graphs/preferences/metadata/VehicleMetadataPreferenceDialogFragment.kt b/app/src/main/java/org/obd/graphs/preferences/metadata/VehicleMetadataPreferenceDialogFragment.kt index 66ec437b6..85518d7fc 100644 --- a/app/src/main/java/org/obd/graphs/preferences/metadata/VehicleMetadataPreferenceDialogFragment.kt +++ b/app/src/main/java/org/obd/graphs/preferences/metadata/VehicleMetadataPreferenceDialogFragment.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R -import org.obd.graphs.bl.datalogger.vehicleCapabilitiesManager +import org.obd.graphs.bl.datalogger.VehicleCapabilitiesManager import org.obd.graphs.preferences.CoreDialogFragment class VehicleMetadataPreferenceDialogFragment : CoreDialogFragment() { @@ -35,7 +35,7 @@ class VehicleMetadataPreferenceDialogFragment : CoreDialogFragment() { requestWindowFeatures() val root = inflater.inflate(R.layout.dialog_vehicle_metadata, container, false) - val adapter = VehicleMetadataViewAdapter(context, vehicleCapabilitiesManager.getVehicleCapabilities()) + val adapter = VehicleMetadataViewAdapter(context, VehicleCapabilitiesManager.getVehicleMetadata()) val recyclerView: RecyclerView = root.findViewById(R.id.recycler_view) recyclerView.layoutManager = GridLayoutManager(context, 1) recyclerView.adapter = adapter diff --git a/app/src/main/java/org/obd/graphs/preferences/pid/PidDefinitionPreferenceDialogFragment.kt b/app/src/main/java/org/obd/graphs/preferences/pid/PidDefinitionPreferenceDialogFragment.kt index 6601adf4b..caa9b717c 100644 --- a/app/src/main/java/org/obd/graphs/preferences/pid/PidDefinitionPreferenceDialogFragment.kt +++ b/app/src/main/java/org/obd/graphs/preferences/pid/PidDefinitionPreferenceDialogFragment.kt @@ -36,9 +36,9 @@ import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R import org.obd.graphs.ViewPreferencesSerializer import org.obd.graphs.bl.datalogger.DataLoggerRepository +import org.obd.graphs.bl.datalogger.VehicleCapabilitiesManager import org.obd.graphs.bl.datalogger.dataLoggerSettings import org.obd.graphs.bl.datalogger.serialize -import org.obd.graphs.bl.datalogger.vehicleCapabilitiesManager import org.obd.graphs.bl.query.Query import org.obd.graphs.bl.query.QueryStrategyType import org.obd.graphs.preferences.CoreDialogFragment @@ -422,7 +422,7 @@ open class PidDefinitionPreferenceDialogFragment( source: Collection, predicate: (PidDefinition) -> Boolean, ): List { - val ecuSupportedPIDs = vehicleCapabilitiesManager.getCapabilities() + val ecuSupportedPIDs = VehicleCapabilitiesManager.getSupportedPIDs() val ecuSupportedPIDsEnabled = Prefs.getBoolean(FILTER_BY_ECU_SUPPORTED_PIDS_PREF, false) val stablePIDsEnabled = Prefs.getBoolean(FILTER_BY_STABLE_PIDS_PREF, false) diff --git a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt index 81b340993..411223a13 100644 --- a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt +++ b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/VehicleCapabilitiesManager.kt @@ -18,7 +18,6 @@ package org.obd.graphs.bl.datalogger import android.util.Log import com.fasterxml.jackson.core.JsonParser -import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.KotlinModule @@ -27,77 +26,88 @@ import org.obd.graphs.preferences.Prefs import org.obd.metrics.api.model.DiagnosticTroubleCode import org.obd.metrics.api.model.VehicleCapabilities -class VehicleMetadata(var name: String, var value: String) +class VehicleMetadata( + var name: String, + var value: String, +) -private const val PREF_VEHICLE_CAPABILITIES = "pref.datalogger.supported.pids" +private const val PREF_VEHICLE_SUPPORTED_PIDS = "pref.datalogger.supported.pids" private const val PREF_VEHICLE_METADATA = "pref.datalogger.vehicle.properties" private const val PREF_DTC = "pref.datalogger.dtc" -class VehicleCapabilitiesManager { - - private val mapper = ObjectMapper().apply { - registerModule(KotlinModule()) - configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) - configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) - configure(JsonParser.Feature.IGNORE_UNDEFINED, true) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - } +object VehicleCapabilitiesManager { + private val mapper = + ObjectMapper().apply { + registerModule(KotlinModule()) + configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) + configure(JsonParser.Feature.IGNORE_UNDEFINED, true) + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + } internal fun updateCapabilities(vehicleCapabilities: VehicleCapabilities) { - Prefs.edit().apply { - Log.i( LOG_TAG, "Property `vehicleCapabilitiesReadingEnabled` is " + - "`${dataLoggerSettings.instance().adapter.vehicleCapabilitiesReadingEnabled}`" + "`${dataLoggerSettings.instance().adapter.vehicleCapabilitiesReadingEnabled}`", ) if (dataLoggerSettings.instance().adapter.vehicleCapabilitiesReadingEnabled) { if (vehicleCapabilities.capabilities.isEmpty()) { - Log.i(LOG_TAG, "Did not receive Vehicle Capabilities. Do not update preferences.") + Log.i( + LOG_TAG, + "Did not receive Vehicle Capabilities. Do not update preferences.", + ) } else { - Log.i(LOG_TAG, "Received Vehicle Capabilities. Updating preferences with=${vehicleCapabilities.capabilities}") - putStringSet(PREF_VEHICLE_CAPABILITIES, vehicleCapabilities.capabilities) + Log.i( + LOG_TAG, + "Received Vehicle Capabilities. Updating preferences with=${vehicleCapabilities.capabilities}", + ) + putStringSet(PREF_VEHICLE_SUPPORTED_PIDS, vehicleCapabilities.capabilities) } } - putString(PREF_VEHICLE_METADATA, mapper.writeValueAsString(vehicleCapabilities.metadata)) - putString(PREF_DTC, mapper.writeValueAsString(vehicleCapabilities.dtc)) + putString( + PREF_VEHICLE_METADATA, + mapper.writeValueAsString(vehicleCapabilities.metadata), + ) + putString(PREF_DTC, mapper.writeValueAsString(vehicleCapabilities.dtc)) commit() } } - fun getCapabilities(): MutableList { + fun getSupportedPIDs(): MutableList { val pidList = DataLoggerRepository.getPidDefinitionRegistry().findAll() - return Prefs.getStringSet(PREF_VEHICLE_CAPABILITIES, emptySet())!!.toMutableList() - .sortedWith(compareBy { t -> pidList.firstOrNull { a -> a.pid == t.uppercase() } }).toMutableList() + return Prefs + .getStringSet(PREF_VEHICLE_SUPPORTED_PIDS, emptySet())!! + .toMutableList() + .sortedWith(compareBy { t -> pidList.firstOrNull { a -> a.pid == t.uppercase() } }) + .toMutableList() } - fun getDiagnosticTroubleCodes(): MutableList = + fun getDiagnosticTroubleCodes(): MutableList = try { var preferences = Prefs.getString(PREF_DTC, "")!! if (preferences.startsWith("\"") && preferences.endsWith("\"")) { preferences = mapper.readValue(preferences) } mapper.readValue>(preferences).toMutableList() - } catch (e: Throwable){ - Log.e(LOG_TAG, "Failed to read Diagnostic Trouble Code from preferences",e) + } catch (e: Throwable) { + Log.e(LOG_TAG, "Failed to read Diagnostic Trouble Code from preferences", e) mutableListOf() } - fun getVehicleCapabilities(): MutableList { - val preferences = Prefs.getString(PREF_VEHICLE_METADATA, "")!! + fun getVehicleMetadata(): MutableList = try { - - return if (preferences.isEmpty()) mutableListOf() else { + val preferences = Prefs.getString(PREF_VEHICLE_METADATA, "")!! + if (preferences.isEmpty()) { + mutableListOf() + } else { val map: Map = mapper.readValue(preferences) - return map.map { (k, v) -> VehicleMetadata(k, v) }.toMutableList() + map.map { (k, v) -> VehicleMetadata(k, v) }.toMutableList() } - } catch (e: Throwable){ - Log.e(LOG_TAG, "Failed to read vehicle capabilities from prefs: '${preferences}'",e) + } catch (e: Throwable) { + Log.e(LOG_TAG, "Failed to read vehicle capabilities from prefs", e) + mutableListOf() } - return mutableListOf() - } } - -val vehicleCapabilitiesManager = VehicleCapabilitiesManager() diff --git a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/WorkflowOrchestrator.kt b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/WorkflowOrchestrator.kt index 2b3d03cfb..fa7bbf5c4 100644 --- a/datalogger/src/main/java/org/obd/graphs/bl/datalogger/WorkflowOrchestrator.kt +++ b/datalogger/src/main/java/org/obd/graphs/bl/datalogger/WorkflowOrchestrator.kt @@ -95,7 +95,7 @@ internal class WorkflowOrchestrator internal constructor() { override fun onRunning(vehicleCapabilities: VehicleCapabilities) { status = WorkflowStatus.Connected Log.i(LOG_TAG, "We are connected to the vehicle: $vehicleCapabilities") - vehicleCapabilitiesManager.updateCapabilities(vehicleCapabilities) + VehicleCapabilitiesManager.updateCapabilities(vehicleCapabilities) sendBroadcastEvent(DATA_LOGGER_CONNECTED_EVENT) // notify about DTC From 10d7ce06e43e7f281dc0404aac192618f74f53cd Mon Sep 17 00:00:00 2001 From: Tomek Zebrowski Date: Sun, 8 Mar 2026 07:48:00 +0100 Subject: [PATCH 05/14] feat: Update layout of DTC dialog --- .../DiagnosticTroubleCodeListPreferences.kt | 2 +- ...sticTroubleCodePreferenceDialogFragment.kt | 66 ++++++---- .../dtc/DiagnosticTroubleCodeViewAdapter.kt | 91 ++++++++++++- app/src/main/res/layout/dialog_dtc.xml | 122 ++++++++---------- app/src/main/res/layout/item_dtc.xml | 113 +++++++++------- 5 files changed, 251 insertions(+), 143 deletions(-) diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeListPreferences.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeListPreferences.kt index 0a7cb97b1..6e1df22c7 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeListPreferences.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeListPreferences.kt @@ -20,7 +20,7 @@ import android.content.Context import android.util.AttributeSet import androidx.preference.DialogPreference -class DiagnosticTroubleCodeListPreferences( + internal class DiagnosticTroubleCodeListPreferences( context: Context, attrs: AttributeSet?, ) : DialogPreference(context, attrs) diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt index e4cf28df5..378b79ca1 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodePreferenceDialogFragment.kt @@ -28,10 +28,8 @@ import org.obd.graphs.preferences.CoreDialogFragment import org.obd.metrics.api.model.DiagnosticTroubleCode import org.obd.metrics.command.dtc.DtcComponent - class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragment() { - - - override fun onCreateView( +internal class DiagnosticTroubleCodePreferenceDialogFragment : CoreDialogFragment() { + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, @@ -39,25 +37,9 @@ import org.obd.metrics.command.dtc.DtcComponent requestWindowFeatures() val root = inflater.inflate(R.layout.dialog_dtc, container, false) - val dtc = VehicleCapabilitiesManager.getDiagnosticTroubleCodes() - if (dtc.isEmpty()) { - val noDTC = DiagnosticTroubleCode( - "", - "", - null, - resources.getString(R.string.pref_dtc_no_dtc_found), - 0, - null, - null, - null, - null, - DtcComponent("","") - ) + val sortedDtcList = diagnosticTroubleCodes() - dtc.add(noDTC) - } - - val adapter = DiagnosticTroubleCodeViewAdapter(context, dtc) + val adapter = DiagnosticTroubleCodeViewAdapter(context, sortedDtcList) val recyclerView: RecyclerView = root.findViewById(R.id.recycler_view) recyclerView.layoutManager = GridLayoutManager(context, 1) recyclerView.adapter = adapter @@ -65,4 +47,44 @@ import org.obd.metrics.command.dtc.DtcComponent attachCloseButton(root) return root } + + private fun diagnosticTroubleCodes(): List { + val diagnosticTroubleCodes = VehicleCapabilitiesManager.getDiagnosticTroubleCodes() + + if (diagnosticTroubleCodes.isEmpty()) { + val noDTC = + DiagnosticTroubleCode( + "", + "", + null, + resources.getString(R.string.pref_dtc_no_dtc_found), + 0, + null, + null, + null, + null, + DtcComponent("", ""), + ) + + diagnosticTroubleCodes.add(noDTC) + } + + val sortedDtcList = + diagnosticTroubleCodes + .sortedWith( + compareBy { code -> + val desc = code.description + val isUnknown = + desc.isNullOrBlank() || + desc.contains( + "Unknown DTC Description", + ignoreCase = true, + ) + if (isUnknown) 1 else 0 + }.thenBy { code -> + code.standardCode + }, + ).toMutableList() + return sortedDtcList + } } diff --git a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt index fcecbc77f..118fe8b00 100644 --- a/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt +++ b/app/src/main/java/org/obd/graphs/preferences/dtc/DiagnosticTroubleCodeViewAdapter.kt @@ -22,6 +22,7 @@ import android.graphics.Typeface import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import org.obd.graphs.R @@ -29,12 +30,14 @@ import org.obd.graphs.ui.common.COLOR_CARDINAL import org.obd.graphs.ui.common.setText import org.obd.metrics.api.model.DiagnosticTroubleCode - class DiagnosticTroubleCodeViewAdapter internal constructor( - context: Context?, - private var data: MutableCollection, +internal class DiagnosticTroubleCodeViewAdapter internal constructor( + context: Context?, + private var data: List, ) : RecyclerView.Adapter() { private val mInflater: LayoutInflater = LayoutInflater.from(context) + private var expandedPosition = RecyclerView.NO_POSITION + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int, @@ -44,9 +47,79 @@ import org.obd.metrics.api.model.DiagnosticTroubleCode holder: ViewHolder, position: Int, ) { - data.elementAt(position).run { - holder.code.setText("${this.standardCode}-${this.failureType.code}", COLOR_CARDINAL, Typeface.NORMAL, 1f) - holder.description.setText(this.description, Color.GRAY, Typeface.NORMAL, 1f) + val item = data.elementAt(position) + + val formattedCode = + if (!item.failureType?.code.isNullOrEmpty()) { + "${item.standardCode}-${item.failureType.code}" + } else { + item.standardCode + } + + var finalDescription = item.description + var isUnknown = false + + if (finalDescription.isNullOrBlank() || + finalDescription.contains( + "Unknown DTC Description", + ignoreCase = true, + ) + ) { + isUnknown = true + val fallbackParts = + listOfNotNull( + item.system?.description, + item.category?.description, + item.subsystem?.description, + ).filter { it.isNotBlank() } + + finalDescription = + if (fallbackParts.isNotEmpty()) { + fallbackParts.joinToString(" → ") + " (Unknown specific fault)" + } else { + "Unknown DTC Description" + } + } + + if (item.standardCode.isEmpty()) { + holder.code.setText("", Color.GRAY, Typeface.NORMAL, 1f) + holder.description.setText(finalDescription, Color.DKGRAY, Typeface.NORMAL, 1f) + holder.expandedContainer.visibility = View.GONE + holder.itemView.setOnClickListener(null) + return + } + + holder.code.setText(formattedCode, COLOR_CARDINAL, Typeface.BOLD, 1f) + if (isUnknown) { + holder.description.setText(finalDescription, Color.GRAY, Typeface.ITALIC, 1f) + } else { + holder.description.setText(finalDescription, Color.DKGRAY, Typeface.NORMAL, 1f) + } + + val isExpanded = position == expandedPosition + holder.expandedContainer.visibility = if (isExpanded) View.VISIBLE else View.GONE + + val systemTxt = item.system?.description ?: "N/A" + val categoryTxt = item.category?.description ?: "N/A" + holder.detailSystemInfo.text = "System: $systemTxt | Category: $categoryTxt" + + val hex = item.rawHex ?: "N/A" + val statusMask = String.format("0x%02X", item.statusMask) + val activeStatuses = item.activeStatuses?.joinToString(", ") ?: "None" + holder.detailHexStatus.text = "Hex: $hex | Mask: $statusMask\nStatus: $activeStatuses" + + holder.itemView.setOnClickListener { + val previousExpandedPosition = expandedPosition + + expandedPosition = + if (isExpanded) { + RecyclerView.NO_POSITION + } else { + position + } + + notifyItemChanged(previousExpandedPosition) + notifyItemChanged(expandedPosition) } } @@ -55,7 +128,11 @@ import org.obd.metrics.api.model.DiagnosticTroubleCode inner class ViewHolder internal constructor( itemView: View, ) : RecyclerView.ViewHolder(itemView) { - var code: TextView = itemView.findViewById(R.id.metadata_value) + var code: TextView = itemView.findViewById(R.id.dtc_value) var description: TextView = itemView.findViewById(R.id.dtc_description) + + var expandedContainer: LinearLayout = itemView.findViewById(R.id.dtc_expanded_details_container) + var detailSystemInfo: TextView = itemView.findViewById(R.id.dtc_detail_system_info) + var detailHexStatus: TextView = itemView.findViewById(R.id.dtc_detail_hex_status) } } diff --git a/app/src/main/res/layout/dialog_dtc.xml b/app/src/main/res/layout/dialog_dtc.xml index 434c72b15..ebfcd0e97 100644 --- a/app/src/main/res/layout/dialog_dtc.xml +++ b/app/src/main/res/layout/dialog_dtc.xml @@ -1,82 +1,68 @@ - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minWidth="350dp" + android:background="@color/white"> - - - - - + app:layout_constraintTop_toTopOf="parent" /> - - - - - + - + + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constrainedHeight="true" + app:layout_constraintBottom_toTopOf="@+id/trip_action_close_window" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/header_divider" /> - - -