From 9e72a2916377782a91f74562945cc1e9df326f19 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:11:26 -0800 Subject: [PATCH 1/9] docs: add design and implementation plan for removing Builder classes Co-Authored-By: Claude Opus 4.6 --- .../2026-02-16-remove-builders-design.md | 37 + docs/plans/2026-02-16-remove-builders.md | 864 ++++++++++++++++++ 2 files changed, 901 insertions(+) create mode 100644 docs/plans/2026-02-16-remove-builders-design.md create mode 100644 docs/plans/2026-02-16-remove-builders.md diff --git a/docs/plans/2026-02-16-remove-builders-design.md b/docs/plans/2026-02-16-remove-builders-design.md new file mode 100644 index 000000000..9efbbd213 --- /dev/null +++ b/docs/plans/2026-02-16-remove-builders-design.md @@ -0,0 +1,37 @@ +# Remove Builder Classes + +## Problem + +The codebase has 4 Builder classes that are not idiomatic Kotlin. Kotlin data classes with named parameters and default values make Builders unnecessary. + +## Builders to Remove + +### 1. ClipperTrip.Builder +- 10 Long fields, all default to 0 +- Replace with: data class constructor with named params, all defaulting to 0 + +### 2. SeqGoTrip.Builder +- 8 fields (ints, nullable Instants, nullable Stations, Mode enum) +- Replace with: data class constructor with named params and defaults + +### 3. Station.Builder +- 8 fields (nullable Strings, one List) +- Replace with: data class constructor with named params, all defaulting to null/emptyList() + +### 4. FareBotUiTree.Builder + Item.Builder +- Hierarchical builder for UI tree structures +- Item.title is currently `String`, resolved from `FormattedString` during `suspend build()` +- `@Serializable` annotation is unnecessary (never serialized) +- Replace with: + - Drop `@Serializable` from FareBotUiTree and Item + - Change `Item.title` from `String` to `FormattedString` + - Remove both Builder classes + - Rewrite `uiTree {}` DSL to build Item objects directly + - Update all ~20 call sites that use the builder directly + +## Approach + +- Change `Item.title` to `FormattedString`, resolve at the UI layer instead +- Rewrite `UiTreeBuilder.kt` DSL to construct items directly (no intermediate Builder) +- For ClipperTrip, SeqGoTrip, Station: add default parameter values, remove Builder + companion factory +- Update all call sites diff --git a/docs/plans/2026-02-16-remove-builders.md b/docs/plans/2026-02-16-remove-builders.md new file mode 100644 index 000000000..97f14c60b --- /dev/null +++ b/docs/plans/2026-02-16-remove-builders.md @@ -0,0 +1,864 @@ +# Remove Builder Classes Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Remove all Builder classes and replace with idiomatic Kotlin patterns (data class constructors, DSL). + +**Architecture:** Three simple Builder classes (ClipperTrip, SeqGoTrip, Station) are replaced with data class constructors using named parameters and defaults. The hierarchical FareBotUiTree Builder is replaced by rewriting the existing `uiTree {}` DSL to build items directly, and converting all 19 imperative builder call sites to use the DSL. + +**Tech Stack:** Kotlin Multiplatform, Compose Multiplatform, compose-resources + +--- + +## Task 1: Rewrite FareBotUiTree data model + +**Files:** +- Modify: `base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt` + +**Step 1: Rewrite FareBotUiTree.kt** + +Replace the entire file with: + +```kotlin +package com.codebutler.farebot.base.ui + +import com.codebutler.farebot.base.util.FormattedString + +data class FareBotUiTree( + val items: List, +) { + data class Item( + val title: FormattedString, + val value: Any? = null, + val children: List = emptyList(), + ) +} +``` + +Key changes: +- Remove `@Serializable` from both classes (never serialized) +- Change `Item.title` from `String` to `FormattedString` +- Remove `Item.Builder`, `FareBotUiTree.Builder`, companion objects +- Remove `@Contextual` annotation on `value` +- `children` and `value` get defaults + +**Step 2: Commit** + +```bash +git add base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt +git commit -m "refactor: simplify FareBotUiTree to plain data classes + +Remove Builder classes and @Serializable annotation. Change Item.title +from String to FormattedString to defer resolution to UI layer." +``` + +--- + +## Task 2: Rewrite UiTreeBuilder DSL + +**Files:** +- Modify: `base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt` + +**Step 1: Rewrite UiTreeBuilder.kt** + +Replace the entire file with: + +```kotlin +package com.codebutler.farebot.base.ui + +import com.codebutler.farebot.base.util.FormattedString +import org.jetbrains.compose.resources.StringResource + +@DslMarker +private annotation class UiTreeBuilderMarker + +fun uiTree(init: TreeScope.() -> Unit): FareBotUiTree { + val scope = TreeScope() + scope.init() + return FareBotUiTree(scope.items.toList()) +} + +@UiTreeBuilderMarker +class TreeScope { + internal val items = mutableListOf() + + fun item(init: ItemScope.() -> Unit) { + val scope = ItemScope() + scope.init() + items.add(scope.build()) + } +} + +@UiTreeBuilderMarker +class ItemScope { + private var _title: FormattedString = FormattedString("") + var title: Any? + get() = _title + set(value) { + _title = when (value) { + is FormattedString -> value + is StringResource -> FormattedString(value) + else -> FormattedString(value.toString()) + } + } + + var value: Any? = null + + private val children = mutableListOf() + + fun item(init: ItemScope.() -> Unit) { + val scope = ItemScope() + scope.init() + children.add(scope.build()) + } + + fun addChildren(items: List) { + children.addAll(items) + } + + internal fun build(): FareBotUiTree.Item = FareBotUiTree.Item( + title = _title, + value = value, + children = children.toList(), + ) +} +``` + +Key changes: +- `uiTree` is no longer `suspend` (no async string resolution during build) +- DSL builds `Item` objects directly instead of delegating to Builder +- `ItemScope.title` accepts `Any?` (String, StringResource, FormattedString) for ergonomics +- `addChildren()` allows appending pre-built items (for OVChipIndex pattern) + +**Step 2: Commit** + +```bash +git add base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt +git commit -m "refactor: rewrite uiTree DSL to build items directly + +No longer wraps Builder classes. The DSL constructs FareBotUiTree.Item +objects directly. uiTree is no longer suspend since FormattedString +resolution is deferred to the UI layer." +``` + +--- + +## Task 3: Update CardAdvancedScreen for FormattedString title + +**Files:** +- Modify: `app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt` + +**Step 1: Change `item.title.orEmpty()` to `item.title.resolve()`** + +In the `TreeItemView` composable, change: +```kotlin +Text( + text = item.title.orEmpty(), +``` +to: +```kotlin +Text( + text = item.title.resolve(), +``` + +The `resolve()` method is a `@Composable` function on `FormattedString` that resolves string resources at render time. + +**Step 2: Commit** + +```bash +git add app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt +git commit -m "refactor: resolve FormattedString title in CardAdvancedScreen" +``` + +--- + +## Task 4: Convert card module getAdvancedUi() to DSL + +**Files (8):** +- `card/classic/src/commonMain/kotlin/.../ClassicCard.kt` +- `card/desfire/src/commonMain/kotlin/.../DesfireCard.kt` +- `card/felica/src/commonMain/kotlin/.../FelicaCard.kt` +- `card/iso7816/src/commonMain/kotlin/.../ISO7816Card.kt` +- `card/cepas/src/commonMain/kotlin/.../CEPASCard.kt` +- `card/ultralight/src/commonMain/kotlin/.../UltralightCard.kt` +- `card/vicinity/src/commonMain/kotlin/.../VicinityCard.kt` +- `card/ksx6924/src/commonMain/kotlin/.../KSX6924PurseInfo.kt` + +**Step 1: Convert each file's getAdvancedUi() from builder to DSL** + +Each file follows the same pattern. Replace: +```kotlin +import com.codebutler.farebot.base.ui.FareBotUiTree +// ... +val builder = FareBotUiTree.builder() +builder.item().title("X").value(y) +return builder.build() +``` +with: +```kotlin +import com.codebutler.farebot.base.ui.uiTree +// ... +return uiTree { + item { title = "X"; value = y } +} +``` + +Specific conversions per file: + +**ClassicCard.kt** — nested sectors with conditional titles: +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + for (sector in sectors) { + val sectorIndexString = sector.index.toString(16) + item { + when (sector) { + is UnauthorizedClassicSector -> { + title = FormattedString(Res.string.classic_unauthorized_sector_title_format, sectorIndexString) + } + is InvalidClassicSector -> { + title = FormattedString(Res.string.classic_invalid_sector_title_format, sectorIndexString, sector.error) + } + else -> { + val dataClassicSector = sector as DataClassicSector + title = FormattedString(Res.string.classic_sector_title_format, sectorIndexString) + for (block in dataClassicSector.blocks) { + item { + title = FormattedString(Res.string.classic_block_title_format, block.index.toString()) + value = block.data + } + } + } + } + } + } +} +``` + +**DesfireCard.kt** — deeply nested apps/files/settings: +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = "Applications" + for (app in applications) { + item { + title = "Application: 0x${app.id.toString(16)}" + item { + title = "Files" + for (file in app.files) { + item { + title = "File: 0x${file.id.toString(16)}" + val fileSettings = file.fileSettings + if (fileSettings != null) { + item { + title = "Settings" + item { title = "Type"; value = fileSettings.fileTypeName } + if (fileSettings is StandardDesfireFileSettings) { + item { title = "Size"; value = fileSettings.fileSize } + } else if (fileSettings is RecordDesfireFileSettings) { + item { title = "Cur Records"; value = fileSettings.curRecords } + item { title = "Max Records"; value = fileSettings.maxRecords } + item { title = "Record Size"; value = fileSettings.recordSize } + } else if (fileSettings is ValueDesfireFileSettings) { + item { title = "Range"; value = "${fileSettings.lowerLimit} - ${fileSettings.upperLimit}" } + item { + title = "Limited Credit" + value = "${fileSettings.limitedCreditValue} (${if (fileSettings.limitedCreditEnabled) "enabled" else "disabled"})" + } + } + } + } + if (file is StandardDesfireFile) { + item { title = "Data"; value = file.data } + } else if (file is RecordDesfireFile) { + item { + title = "Records" + for (i in file.records.indices) { + item { title = "Record $i"; value = file.records[i].data } + } + } + } else if (file is ValueDesfireFile) { + item { title = "Value"; value = file.value } + } else if (file is InvalidDesfireFile) { + item { title = "Error"; value = file.errorMessage } + } else if (file is UnauthorizedDesfireFile) { + item { title = "Error"; value = file.errorMessage } + } + } + } + } + } + } + } + item { + title = "Manufacturing Data" + item { + title = "Hardware Information" + item { title = "Vendor ID"; value = manufacturingData.hwVendorID } + item { title = "Type"; value = manufacturingData.hwType } + item { title = "Subtype"; value = manufacturingData.hwSubType } + item { title = "Major Version"; value = manufacturingData.hwMajorVersion } + item { title = "Minor Version"; value = manufacturingData.hwMinorVersion } + item { title = "Storage Size"; value = manufacturingData.hwStorageSize } + item { title = "Protocol"; value = manufacturingData.hwProtocol } + } + item { + title = "Software Information" + item { title = "Vendor ID"; value = manufacturingData.swVendorID } + item { title = "Type"; value = manufacturingData.swType } + item { title = "Subtype"; value = manufacturingData.swSubType } + item { title = "Major Version"; value = manufacturingData.swMajorVersion } + item { title = "Minor Version"; value = manufacturingData.swMinorVersion } + item { title = "Storage Size"; value = manufacturingData.swStorageSize } + item { title = "Protocol"; value = manufacturingData.swProtocol } + } + item { + title = "General Information" + item { title = "Serial Number"; value = manufacturingData.uidHex } + item { title = "Batch Number"; value = manufacturingData.batchNoHex } + item { title = "Week of Production"; value = manufacturingData.weekProd.toString(16) } + item { title = "Year of Production"; value = manufacturingData.yearProd.toString(16) } + } + } +} +``` + +**FelicaCard.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = "IDm"; value = idm } + item { title = "PMm"; value = pmm } + item { + title = "Systems" + for (system in systems) { + item { + title = "System: ${system.code.toString(16)}" + for (service in system.services) { + item { + title = "Service: 0x${service.serviceCode.toString(16)} (${FelicaUtils.getFriendlyServiceName(system.code, service.serviceCode)})" + for (block in service.blocks) { + item { + title = "Block ${block.address.toString().padStart(2, '0')}" + value = block.data + } + } + } + } + } + } + } +} +``` + +**ISO7816Card.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = "Applications" + for (app in applications) { + item { + val appNameStr = app.appName?.let { formatAID(it) } ?: "Unknown" + title = "Application: $appNameStr (${app.type})" + if (app.files.isNotEmpty()) { + item { + title = "Files" + for ((selector, file) in app.files) { + item { + title = "File: $selector" + if (file.binaryData != null) { + item { title = "Binary Data"; value = file.binaryData } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { title = "Record $index"; value = record } + } + } + } + } + } + if (app.sfiFiles.isNotEmpty()) { + item { + title = "SFI Files" + for ((sfi, file) in app.sfiFiles.entries.sortedBy { it.key }) { + item { + title = "SFI 0x${sfi.toString(16)}" + if (file.binaryData != null) { + item { title = "Binary Data"; value = file.binaryData } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { title = "Record $index"; value = record } + } + } + } + } + } + } + } + } +} +``` + +**CEPASCard.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + for (purse in purses) { + item { + title = "Purse ID ${purse.id}" + item { title = "CEPAS Version"; value = purse.cepasVersion } + item { title = "Purse Status"; value = purse.purseStatus } + item { title = "Purse Balance"; value = CurrencyFormatter.formatValue(purse.purseBalance / 100.0, "SGD") } + item { title = "Purse Creation Date"; value = formatDate(Instant.fromEpochMilliseconds(purse.purseCreationDate * 1000L), DateFormatStyle.LONG) } + item { title = "Purse Expiry Date"; value = formatDate(Instant.fromEpochMilliseconds(purse.purseExpiryDate * 1000L), DateFormatStyle.LONG) } + item { title = "Autoload Amount"; value = purse.autoLoadAmount } + item { title = "CAN"; value = purse.can } + item { title = "CSN"; value = purse.csn } + } + item { + title = "Last Transaction Information" + item { title = "TRP"; value = purse.lastTransactionTRP } + item { title = "Credit TRP"; value = purse.lastCreditTransactionTRP } + item { title = "Credit Header"; value = purse.lastCreditTransactionHeader } + item { title = "Debit Options"; value = purse.lastTransactionDebitOptionsByte } + } + item { + title = "Other Purse Information" + item { title = "Logfile Record Count"; value = purse.logfileRecordCount } + item { title = "Issuer Data Length"; value = purse.issuerDataLength } + item { title = "Issuer-specific Data"; value = purse.issuerSpecificData } + } + } +} +``` + +**UltralightCard.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = Res.string.ultralight_pages + for (page in pages) { + item { + title = FormattedString(Res.string.ultralight_page_title_format, page.index.toString()) + value = page.data + } + } + } +} +``` + +**VicinityCard.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + if (sysInfo != null) { + item { title = "System Info"; value = sysInfo } + } + item { + title = "Pages" + for (page in pages) { + item { + title = "Page ${page.index}" + value = if (page.isUnauthorized) "Unauthorized" else page.data + } + } + } +} +``` + +**KSX6924PurseInfo.kt:** +```kotlin +suspend fun getAdvancedInfo(resolver: KSX6924PurseInfoResolver = KSX6924PurseInfoDefaultResolver): FareBotUiTree = uiTree { + item { title = Res.string.ksx6924_crypto_algorithm; value = resolver.resolveCryptoAlgo(alg) } + item { title = Res.string.ksx6924_encryption_key_version; value = vk.hexString } + item { title = Res.string.ksx6924_auth_id; value = idtr.hexString } + item { title = Res.string.ksx6924_ticket_type; value = resolver.resolveUserCode(userCode) } + item { title = Res.string.ksx6924_max_balance; value = balMax.toString() } + item { title = Res.string.ksx6924_branch_code; value = bra.hexString } + item { title = Res.string.ksx6924_one_time_limit; value = mmax.toString() } + item { title = Res.string.ksx6924_mobile_carrier; value = resolver.resolveTCode(tcode) } + item { title = Res.string.ksx6924_financial_institution; value = resolver.resolveCCode(ccode) } + item { title = Res.string.ksx6924_rfu; value = rfu.hex() } +} +``` + +For each file, update imports: replace `import com.codebutler.farebot.base.ui.FareBotUiTree` with `import com.codebutler.farebot.base.ui.uiTree` (and keep FareBotUiTree import only if the type is referenced in return type annotations). + +**Step 2: Commit** + +```bash +git add card/ +git commit -m "refactor: convert card getAdvancedUi() from builder to DSL" +``` + +--- + +## Task 5: Convert transit module getAdvancedUi() to DSL + +**Files (12):** +- `transit/ovc/src/commonMain/kotlin/.../OVChipTransitInfo.kt` +- `transit/ovc/src/commonMain/kotlin/.../OVChipIndex.kt` +- `transit/octopus/src/commonMain/kotlin/.../OctopusTransitInfo.kt` +- `transit/smartrider/src/commonMain/kotlin/.../SmartRiderTransitInfo.kt` +- `transit/hsl/src/commonMain/kotlin/.../HSLTransitInfo.kt` +- `transit/charlie/src/commonMain/kotlin/.../CharlieCardTransitInfo.kt` +- `transit/nextfareul/src/commonMain/kotlin/.../NextfareUltralightTransitData.kt` +- `transit/calypso/src/commonMain/kotlin/.../LisboaVivaTransitInfo.kt` +- `transit/serialonly/src/commonMain/kotlin/.../StrelkaTransitInfo.kt` +- `transit/serialonly/src/commonMain/kotlin/.../HoloTransitInfo.kt` +- `transit/umarsh/src/commonMain/kotlin/.../UmarshTransitInfo.kt` +- `transit/troika/src/commonMain/kotlin/.../TroikaHybridTransitInfo.kt` + +**Step 1: Convert OVChipIndex.addAdvancedItems** + +Change from taking `FareBotUiTree.Item.Builder` to returning `List`: + +```kotlin +fun advancedItems(): List = listOf( + FareBotUiTree.Item(title = FormattedString("Transaction Slot"), value = if (recentTransactionSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Info Slot"), value = if (recentInfoSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Subscription Slot"), value = if (recentSubscriptionSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Travelhistory Slot"), value = if (recentTravelhistorySlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Credit Slot"), value = if (recentCreditSlot) "B" else "A"), +) +``` + +**Step 2: Convert OVChipTransitInfo.getAdvancedUi()** + +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = "Credit Slot ID"; value = creditSlotId.toString() } + item { title = "Last Credit ID"; value = creditId.toString() } + item { + title = "Recent Slots" + addChildren(index.advancedItems()) + } +} +``` + +**Step 3: Convert remaining transit files** + +Each follows the same builder-to-DSL pattern. Specific conversions: + +**OctopusTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + val szt = shenzhenBalance + if (!hasOctopus || szt == null) return null + return uiTree { + item { + title = Res.string.octopus_alternate_purse_balances + item { + title = Res.string.octopus_szt + value = TransitCurrency.CNY(szt).formatCurrencyString(isBalance = true) + } + } + } +} +``` + +**SmartRiderTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.smartrider_ticket_type; value = mTokenType.toString() } + if (mSmartRiderType == SmartRiderType.SMARTRIDER) { + item { title = Res.string.smartrider_autoload_threshold; value = TransitCurrency.AUD(mAutoloadThreshold).formatCurrencyString(true) } + item { title = Res.string.smartrider_autoload_value; value = TransitCurrency.AUD(mAutoloadValue).formatCurrencyString(true) } + } +} +``` + +**HSLTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + val tree = uiTree { + applicationVersion?.let { item { title = Res.string.hsl_application_version; value = it } } + applicationKeyVersion?.let { item { title = Res.string.hsl_application_key_version; value = it } } + platformType?.let { item { title = Res.string.hsl_platform_type; value = it } } + securityLevel?.let { item { title = Res.string.hsl_security_level; value = it } } + } + return if (tree.items.isEmpty()) null else tree +} +``` + +**CharlieCardTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + if (secondSerial == 0L || secondSerial == 0xffffffffL) return null + return uiTree { + item { title = Res.string.charlie_2nd_card_number; value = "A" + NumberUtils.zeroPad(secondSerial, 10) } + } +} +``` + +**NextfareUltralightTransitData.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.nextfareul_machine_code; value = capsule.mMachineCode.toString(16) } +} +``` + +**LisboaVivaTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + if (tagId == null) return null + return uiTree { + item { title = Res.string.calypso_engraved_serial; value = tagId.toString() } + } +} +``` + +**StrelkaTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.strelka_long_serial; value = mSerial } +} +``` + +**HoloTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.manufacture_id; value = mManufacturingId } +} +``` + +**UmarshTransitInfo.kt:** +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + val rubSectors = sectors.filter { it.denomination == UmarshDenomination.RUB } + if (rubSectors.isEmpty()) return null + return uiTree { + for (sec in rubSectors) { + item { title = Res.string.umarsh_machine_id; value = sec.machineId.toString() } + } + } +} +``` + +**TroikaHybridTransitInfo.kt** — flattens sub-trees: +```kotlin +override suspend fun getAdvancedUi(): FareBotUiTree? { + val trees = listOfNotNull( + troika.getAdvancedUi(), + podorozhnik?.getAdvancedUi(), + strelka?.getAdvancedUi(), + ) + if (trees.isEmpty()) return null + return FareBotUiTree(items = trees.flatMap { tree -> + tree.items.map { FareBotUiTree.Item(title = it.title, value = it.value) } + }) +} +``` + +**Step 4: Commit** + +```bash +git add transit/ +git commit -m "refactor: convert transit getAdvancedUi() from builder to DSL" +``` + +--- + +## Task 6: Remove ClipperTrip.Builder + +**Files:** +- Modify: `transit/clipper/src/commonMain/kotlin/.../ClipperTrip.kt` +- Modify: `transit/clipper/src/commonMain/kotlin/.../ClipperTransitFactory.kt` +- Modify: `app/src/commonTest/kotlin/.../ClipperTransitTest.kt` + +**Step 1: Add default values to ClipperTrip constructor, remove Builder** + +Add `= 0` defaults to all constructor parameters (vehicleNum and transportCode already have them). Remove the `Builder` class and `companion object`: + +```kotlin +class ClipperTrip( + private val timestamp: Long = 0, + private val exitTimestampValue: Long = 0, + private val balance: Long = 0, + private val fareValue: Long = 0, + private val agency: Long = 0, + private val from: Long = 0, + private val to: Long = 0, + private val route: Long = 0, + private val vehicleNum: Long = 0, + private val transportCode: Long = 0, +) : Trip() { + // ... all properties and methods stay the same ... + // DELETE: companion object { fun builder() } + // DELETE: class Builder { ... } +} +``` + +**Step 2: Update ClipperTransitFactory.createTrip()** + +Replace: +```kotlin +return ClipperTrip + .builder() + .timestamp(timestamp) + .exitTimestamp(exitTimestamp) + // ... + .build() +``` +with: +```kotlin +return ClipperTrip( + timestamp = timestamp, + exitTimestampValue = exitTimestamp, + balance = 0, + fareValue = fare, + agency = agency, + from = from, + to = to, + route = route, + vehicleNum = vehicleNum, + transportCode = transportCode, +) +``` + +**Step 3: Update ClipperTransitTest.kt** + +Replace all `ClipperTrip.builder().agency(x).transportCode(y).build()` with constructor calls: +```kotlin +// Before: +val trip = ClipperTrip.builder().agency(0x04).transportCode(0x6f).build() +// After: +val trip = ClipperTrip(agency = 0x04, transportCode = 0x6f) +``` + +Apply this to every test method (~15 call sites). + +**Step 4: Commit** + +```bash +git add transit/clipper/ app/src/commonTest/ +git commit -m "refactor: remove ClipperTrip.Builder, use constructor with named params" +``` + +--- + +## Task 7: Remove SeqGoTrip.Builder + +**Files:** +- Modify: `transit/seqgo/src/commonMain/kotlin/.../SeqGoTrip.kt` +- Modify: `transit/seqgo/src/commonMain/kotlin/.../SeqGoTransitFactory.kt` + +**Step 1: Add default values to SeqGoTrip constructor, remove Builder** + +```kotlin +class SeqGoTrip( + private val journeyId: Int = 0, + private val modeValue: Mode = Mode.OTHER, + private val startTime: Instant? = null, + private val endTime: Instant? = null, + private val startStationId: Int = 0, + private val endStationId: Int = 0, + private val startStationValue: Station? = null, + private val endStationValue: Station? = null, +) : Trip() { + // ... all properties and methods stay the same ... + // DELETE: class Builder { ... } + // DELETE: companion object { fun builder() } +} +``` + +**Step 2: Update SeqGoTransitFactory** + +Replace builder usage with direct construction. The tricky part: the builder pattern was used to conditionally add tap-off data. Use `var` locals + copy pattern, or build the trip in one expression: + +```kotlin +val tapOn = sortedTaps[i] +var endTime: Instant? = null +var endStationId = 0 +var endStation: Station? = null + +if (sortedTaps.size > i + 1 && + sortedTaps[i + 1].journey == tapOn.journey && + sortedTaps[i + 1].mode == tapOn.mode +) { + val tapOff = sortedTaps[i + 1] + endTime = tapOff.timestamp + endStationId = tapOff.station + endStation = SeqGoUtil.getStation(tapOff.station) + i++ +} + +trips.add( + SeqGoTrip( + journeyId = tapOn.journey, + modeValue = tapOn.mode, + startTime = tapOn.timestamp, + endTime = endTime, + startStationId = tapOn.station, + endStationId = endStationId, + startStationValue = SeqGoUtil.getStation(tapOn.station), + endStationValue = endStation, + ) +) +``` + +**Step 3: Commit** + +```bash +git add transit/seqgo/ +git commit -m "refactor: remove SeqGoTrip.Builder, use constructor with named params" +``` + +--- + +## Task 8: Remove Station.Builder + +**Files:** +- Modify: `transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt` + +**Step 1: Remove Builder class and companion factory method** + +Delete the `Builder` class (lines 110-170) and the `builder()` method from the companion object. The `Station` data class already has default values for all constructor parameters. No call sites to update (0 usages). + +**Step 2: Commit** + +```bash +git add transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt +git commit -m "refactor: remove unused Station.Builder" +``` + +--- + +## Task 9: Build and test + +**Step 1: Run tests** + +```bash +cd /workspace/.worktrees/remove-builders && ./gradlew allTests +``` + +Expected: All tests pass. + +**Step 2: Run full build** + +```bash +cd /workspace/.worktrees/remove-builders && ./gradlew assemble +``` + +Expected: Build succeeds. + +**Step 3: Fix any compilation errors** + +Common issues to watch for: +- Missing imports for `uiTree` or `FormattedString` +- `FareBotUiTree.builder()` references still lingering +- `FareBotUiTree.Item.Builder` type references in function signatures (OVChipIndex) + +--- + +## Task Dependency Graph + +``` +Task 1 (FareBotUiTree data model) + └─ Task 2 (UiTreeBuilder DSL) + ├─ Task 3 (CardAdvancedScreen) + ├─ Task 4 (card modules) ──┐ + └─ Task 5 (transit modules)┤ + ├─ Task 9 (build & test) +Task 6 (ClipperTrip.Builder) ─────┤ +Task 7 (SeqGoTrip.Builder) ───────┤ +Task 8 (Station.Builder) ─────────┘ +``` + +Tasks 6, 7, 8 are independent of tasks 1-5 and can run in parallel. +Tasks 4 and 5 can run in parallel after tasks 1-3 are complete. From 9f7e5611abcdcaf3b52369cab57a9d871662e3b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:15:20 -0800 Subject: [PATCH 2/9] refactor: replace FareBotUiTree/UiTreeBuilder builders with data classes and DSL - FareBotUiTree: remove @Serializable, Builder, companion objects; Item.title is now FormattedString (deferred resolution) instead of String - UiTreeBuilder: uiTree() is no longer suspend; DSL builds Item objects directly; ItemScope.title accepts Any? (String/StringResource/FormattedString); add addChildren() for pre-built item lists - CardAdvancedScreen: item.title.orEmpty() -> item.title.resolve() since title is now FormattedString Co-Authored-By: Claude Opus 4.6 --- .../shared/ui/screen/CardAdvancedScreen.kt | 2 +- .../farebot/base/ui/FareBotUiTree.kt | 91 +------------------ .../farebot/base/ui/UiTreeBuilder.kt | 53 +++++++---- 3 files changed, 39 insertions(+), 107 deletions(-) diff --git a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt index afa259071..835471365 100644 --- a/app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt +++ b/app/src/commonMain/kotlin/com/codebutler/farebot/shared/ui/screen/CardAdvancedScreen.kt @@ -130,7 +130,7 @@ private fun TreeItemView( Column(modifier = Modifier.weight(1f)) { Text( - text = item.title.orEmpty(), + text = item.title.resolve(), style = MaterialTheme.typography.bodyMedium, ) if (item.value != null) { diff --git a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt index 2d4569a2d..035c7810b 100644 --- a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt +++ b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/FareBotUiTree.kt @@ -1,96 +1,13 @@ package com.codebutler.farebot.base.ui import com.codebutler.farebot.base.util.FormattedString -import kotlinx.serialization.Contextual -import kotlinx.serialization.Serializable -import org.jetbrains.compose.resources.StringResource -@Serializable data class FareBotUiTree( val items: List, ) { - companion object { - fun builder(): Builder = Builder() - - private suspend fun buildItems(itemBuilders: List): List = itemBuilders.map { it.build() } - } - - class Builder { - private val itemBuilders = mutableListOf() - - fun item(): Item.Builder { - val builder = Item.builder() - itemBuilders.add(builder) - return builder - } - - suspend fun build(): FareBotUiTree = FareBotUiTree(buildItems(itemBuilders)) - } - - @Serializable data class Item( - val title: String, - @Contextual val value: Any?, - val children: List, - ) { - companion object { - fun builder(): Builder = Builder() - } - - class Builder { - private var title: FormattedString = FormattedString("") - private var value: Any? = null - private val childBuilders = mutableListOf() - - fun title(text: String): Builder { - this.title = FormattedString(text) - return this - } - - fun title(textRes: StringResource): Builder { - this.title = FormattedString(textRes) - return this - } - - fun title(formattedString: FormattedString): Builder { - this.title = formattedString - return this - } - - fun value(value: Any?): Builder { - this.value = value - return this - } - - fun item(): Builder { - val builder = Item.builder() - childBuilders.add(builder) - return builder - } - - fun item( - title: String, - value: Any?, - ): Builder = - item() - .title(title) - .value(value) - - fun item( - title: StringResource, - value: Any?, - ): Builder = - item().also { - it.title = FormattedString(title) - it.value(value) - } - - suspend fun build(): Item = - Item( - title = title.resolveAsync(), - value = value, - children = buildItems(childBuilders), - ) - } - } + val title: FormattedString, + val value: Any? = null, + val children: List = emptyList(), + ) } diff --git a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt index 07e6d040f..d6413f60c 100644 --- a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt +++ b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt @@ -22,44 +22,59 @@ package com.codebutler.farebot.base.ui +import com.codebutler.farebot.base.util.FormattedString import org.jetbrains.compose.resources.StringResource @DslMarker private annotation class UiTreeBuilderMarker -suspend fun uiTree(init: TreeScope.() -> Unit): FareBotUiTree { - val uiBuilder = FareBotUiTree.builder() - TreeScope(uiBuilder).init() - return uiBuilder.build() +fun uiTree(init: TreeScope.() -> Unit): FareBotUiTree { + val scope = TreeScope() + scope.init() + return FareBotUiTree(scope.items.toList()) } @UiTreeBuilderMarker -class TreeScope( - private val uiBuilder: FareBotUiTree.Builder, -) { +class TreeScope { + internal val items = mutableListOf() + fun item(init: ItemScope.() -> Unit) { - ItemScope(uiBuilder.item()).init() + val scope = ItemScope() + scope.init() + items.add(scope.build()) } } @UiTreeBuilderMarker -class ItemScope( - private val item: FareBotUiTree.Item.Builder, -) { - var title: Any? = null +class ItemScope { + private var _title: FormattedString = FormattedString("") + var title: Any? + get() = _title set(value) { - when (value) { - is StringResource -> item.title(value) - else -> item.title(value.toString()) + _title = when (value) { + is FormattedString -> value + is StringResource -> FormattedString(value) + else -> FormattedString(value.toString()) } } var value: Any? = null - set(value) { - item.value(value) - } + + private val children = mutableListOf() fun item(init: ItemScope.() -> Unit) { - ItemScope(item.item()).init() + val scope = ItemScope() + scope.init() + children.add(scope.build()) } + + fun addChildren(items: List) { + children.addAll(items) + } + + internal fun build(): FareBotUiTree.Item = FareBotUiTree.Item( + title = _title, + value = value, + children = children.toList(), + ) } From dc4662c966b47462e57d44ce6e615b67e954d0fe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:19:24 -0800 Subject: [PATCH 3/9] refactor: remove ClipperTrip.Builder, use constructor with named params Co-Authored-By: Claude Opus 4.6 --- .../farebot/test/ClipperTransitTest.kt | 173 ++++++++---------- .../transit/clipper/ClipperTransitFactory.kt | 24 ++- .../farebot/transit/clipper/ClipperTrip.kt | 66 +------ 3 files changed, 96 insertions(+), 167 deletions(-) diff --git a/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt b/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt index 10b014b8a..4cbd97187 100644 --- a/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt +++ b/app/src/commonTest/kotlin/com/codebutler/farebot/test/ClipperTransitTest.kt @@ -106,11 +106,10 @@ class ClipperTransitTest { fun testClipperTripModeDetection_BART() { // BART with transportCode 0x6f -> METRO val trip = - ClipperTrip - .builder() - .agency(0x04) // AGENCY_BART - .transportCode(0x6f) - .build() + ClipperTrip( + agency = 0x04, // AGENCY_BART + transportCode = 0x6f, + ) assertEquals(Trip.Mode.METRO, trip.mode) } @@ -118,11 +117,10 @@ class ClipperTransitTest { fun testClipperTripModeDetection_MuniLightRail() { // Muni with transportCode 0x62 -> TRAM (default) val trip = - ClipperTrip - .builder() - .agency(0x12) // AGENCY_MUNI - .transportCode(0x62) - .build() + ClipperTrip( + agency = 0x12, // AGENCY_MUNI + transportCode = 0x62, + ) assertEquals(Trip.Mode.TRAM, trip.mode) } @@ -130,11 +128,10 @@ class ClipperTransitTest { fun testClipperTripModeDetection_Caltrain() { // Caltrain with transportCode 0x62 -> TRAIN val trip = - ClipperTrip - .builder() - .agency(0x06) // AGENCY_CALTRAIN - .transportCode(0x62) - .build() + ClipperTrip( + agency = 0x06, // AGENCY_CALTRAIN + transportCode = 0x62, + ) assertEquals(Trip.Mode.TRAIN, trip.mode) } @@ -142,11 +139,10 @@ class ClipperTransitTest { fun testClipperTripModeDetection_SMART() { // SMART with transportCode 0x62 -> TRAIN val trip = - ClipperTrip - .builder() - .agency(0x0c) // AGENCY_SMART - .transportCode(0x62) - .build() + ClipperTrip( + agency = 0x0c, // AGENCY_SMART + transportCode = 0x62, + ) assertEquals(Trip.Mode.TRAIN, trip.mode) } @@ -154,11 +150,10 @@ class ClipperTransitTest { fun testClipperTripModeDetection_GGFerry() { // GG Ferry with transportCode 0x62 -> FERRY val trip = - ClipperTrip - .builder() - .agency(0x19) // AGENCY_GG_FERRY - .transportCode(0x62) - .build() + ClipperTrip( + agency = 0x19, // AGENCY_GG_FERRY + transportCode = 0x62, + ) assertEquals(Trip.Mode.FERRY, trip.mode) } @@ -166,44 +161,40 @@ class ClipperTransitTest { fun testClipperTripModeDetection_SFBayFerry() { // SF Bay Ferry with transportCode 0x62 -> FERRY val trip = - ClipperTrip - .builder() - .agency(0x1b) // AGENCY_SF_BAY_FERRY - .transportCode(0x62) - .build() + ClipperTrip( + agency = 0x1b, // AGENCY_SF_BAY_FERRY + transportCode = 0x62, + ) assertEquals(Trip.Mode.FERRY, trip.mode) } @Test fun testClipperTripModeDetection_Bus() { val trip = - ClipperTrip - .builder() - .agency(0x01) // AGENCY_ACTRAN - .transportCode(0x61) - .build() + ClipperTrip( + agency = 0x01, // AGENCY_ACTRAN + transportCode = 0x61, + ) assertEquals(Trip.Mode.BUS, trip.mode) } @Test fun testClipperTripModeDetection_Unknown() { val trip = - ClipperTrip - .builder() - .agency(0x04) // AGENCY_BART - .transportCode(0xFF) - .build() + ClipperTrip( + agency = 0x04, // AGENCY_BART + transportCode = 0xFF, + ) assertEquals(Trip.Mode.OTHER, trip.mode) } @Test fun testClipperTripFareCurrency() { val trip = - ClipperTrip - .builder() - .agency(0x04) - .fare(350) - .build() + ClipperTrip( + agency = 0x04, + fareValue = 350, + ) val fareStr = trip.fare?.formatCurrencyString() ?: "" // Should format as USD assertTrue( @@ -215,12 +206,11 @@ class ClipperTransitTest { @Test fun testClipperTripWithBalance() { val trip = - ClipperTrip - .builder() - .agency(0x04) - .fare(200) - .balance(1000) - .build() + ClipperTrip( + agency = 0x04, + fareValue = 200, + balance = 1000, + ) val updated = trip.withBalance(500) assertEquals(500L, updated.getBalance()) } @@ -297,58 +287,52 @@ class ClipperTransitTest { fun testVehicleNumbers() { // Test null vehicle number (0) val trip0 = - ClipperTrip - .builder() - .agency(0x12) // Muni - .vehicleNum(0) - .build() + ClipperTrip( + agency = 0x12, // Muni + vehicleNum = 0, + ) assertNull(trip0.vehicleID) // Test null vehicle number (0xffff) val tripFfff = - ClipperTrip - .builder() - .agency(0x12) - .vehicleNum(0xffff) - .build() + ClipperTrip( + agency = 0x12, + vehicleNum = 0xffff, + ) assertNull(tripFfff.vehicleID) // Test regular vehicle number val trip1058 = - ClipperTrip - .builder() - .agency(0x12) - .vehicleNum(1058) - .build() + ClipperTrip( + agency = 0x12, + vehicleNum = 1058, + ) assertEquals("1058", trip1058.vehicleID) // Test regular vehicle number val trip1525 = - ClipperTrip - .builder() - .agency(0x12) - .vehicleNum(1525) - .build() + ClipperTrip( + agency = 0x12, + vehicleNum = 1525, + ) assertEquals("1525", trip1525.vehicleID) // Test LRV4 Muni vehicle numbers (5 digits, encoded as number*10 + letter) // 2010A = 20100 + 1 - 1 = 20101? No, the encoding is: number/10 gives the vehicle, %10 gives letter offset // 20101: 20101/10 = 2010, 20101%10 = 1, letter = 9+1 = A (in hex, 10 = A) val trip2010A = - ClipperTrip - .builder() - .agency(0x12) - .vehicleNum(20101) - .build() + ClipperTrip( + agency = 0x12, + vehicleNum = 20101, + ) assertEquals("2010A", trip2010A.vehicleID) // 2061B = vehicle/10 = 2061, letter offset = 2 -> 9+2 = B (11 in hex = B) val trip2061B = - ClipperTrip - .builder() - .agency(0x12) - .vehicleNum(20612) - .build() + ClipperTrip( + agency = 0x12, + vehicleNum = 20612, + ) assertEquals("2061B", trip2061B.vehicleID) } @@ -356,28 +340,25 @@ class ClipperTransitTest { fun testHumanReadableRouteID() { // Golden Gate Ferry should display route ID in hex val ggFerryTrip = - ClipperTrip - .builder() - .agency(0x19) // AGENCY_GG_FERRY - .route(0x1234) - .build() + ClipperTrip( + agency = 0x19, // AGENCY_GG_FERRY + route = 0x1234, + ) assertEquals("0x1234", ggFerryTrip.humanReadableRouteID) // Other agencies should not have humanReadableRouteID val bartTrip = - ClipperTrip - .builder() - .agency(0x04) // AGENCY_BART - .route(0x5678) - .build() + ClipperTrip( + agency = 0x04, // AGENCY_BART + route = 0x5678, + ) assertNull(bartTrip.humanReadableRouteID) val muniTrip = - ClipperTrip - .builder() - .agency(0x12) // AGENCY_MUNI - .route(0xABCD) - .build() + ClipperTrip( + agency = 0x12, // AGENCY_MUNI + route = 0xABCD, + ) assertNull(muniTrip.humanReadableRouteID) } diff --git a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTransitFactory.kt b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTransitFactory.kt index 5e41d51ed..435ff1351 100644 --- a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTransitFactory.kt +++ b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTransitFactory.kt @@ -222,19 +222,17 @@ class ClipperTransitFactory : TransitFactory { return null } - return ClipperTrip - .builder() - .timestamp(timestamp) - .exitTimestamp(exitTimestamp) - .fare(fare) - .agency(agency) - .from(from) - .to(to) - .route(route) - .vehicleNum(vehicleNum) - .transportCode(transportCode) - .balance(0) // Filled in later - .build() + return ClipperTrip( + timestamp = timestamp, + exitTimestampValue = exitTimestamp, + fareValue = fare, + agency = agency, + from = from, + to = to, + route = route, + vehicleNum = vehicleNum, + transportCode = transportCode, + ) } private fun parseRefills(card: DesfireCard): List { diff --git a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt index 2f6c836f8..5cdcca59e 100644 --- a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt +++ b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt @@ -34,14 +34,14 @@ import com.codebutler.farebot.transit.Trip import kotlin.time.Instant class ClipperTrip( - private val timestamp: Long, - private val exitTimestampValue: Long, - private val balance: Long, - private val fareValue: Long, - private val agency: Long, - private val from: Long, - private val to: Long, - private val route: Long, + private val timestamp: Long = 0, + private val exitTimestampValue: Long = 0, + private val balance: Long = 0, + private val fareValue: Long = 0, + private val agency: Long = 0, + private val from: Long = 0, + private val to: Long = 0, + private val route: Long = 0, private val vehicleNum: Long = 0, private val transportCode: Long = 0, ) : Trip() { @@ -127,54 +127,4 @@ class ClipperTrip( transportCode, ) - companion object { - fun builder(): Builder = Builder() - } - - class Builder { - private var timestamp: Long = 0 - private var exitTimestamp: Long = 0 - private var balance: Long = 0 - private var fare: Long = 0 - private var agency: Long = 0 - private var from: Long = 0 - private var to: Long = 0 - private var route: Long = 0 - private var vehicleNum: Long = 0 - private var transportCode: Long = 0 - - fun timestamp(timestamp: Long): Builder = apply { this.timestamp = timestamp } - - fun exitTimestamp(exitTimestamp: Long): Builder = apply { this.exitTimestamp = exitTimestamp } - - fun balance(balance: Long): Builder = apply { this.balance = balance } - - fun fare(fare: Long): Builder = apply { this.fare = fare } - - fun agency(agency: Long): Builder = apply { this.agency = agency } - - fun from(from: Long): Builder = apply { this.from = from } - - fun to(to: Long): Builder = apply { this.to = to } - - fun route(route: Long): Builder = apply { this.route = route } - - fun vehicleNum(vehicleNum: Long): Builder = apply { this.vehicleNum = vehicleNum } - - fun transportCode(transportCode: Long): Builder = apply { this.transportCode = transportCode } - - fun build(): ClipperTrip = - ClipperTrip( - timestamp, - exitTimestamp, - balance, - fare, - agency, - from, - to, - route, - vehicleNum, - transportCode, - ) - } } From 8316556b48c1326bb7a49d12ea956164cd091ad2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:19:29 -0800 Subject: [PATCH 4/9] refactor: remove SeqGoTrip.Builder, use constructor with named params Co-Authored-By: Claude Opus 4.6 --- .../transit/seqgo/SeqGoTransitFactory.kt | 31 ++++++---- .../farebot/transit/seqgo/SeqGoTrip.kt | 58 +++---------------- 2 files changed, 28 insertions(+), 61 deletions(-) diff --git a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt index 805531c33..3a841ccd8 100644 --- a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt +++ b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt @@ -27,10 +27,12 @@ import com.codebutler.farebot.card.classic.ClassicCard import com.codebutler.farebot.card.classic.DataClassicSector import com.codebutler.farebot.transit.CardInfo import com.codebutler.farebot.transit.Refill +import com.codebutler.farebot.transit.Station import com.codebutler.farebot.transit.TransitFactory import com.codebutler.farebot.transit.TransitIdentity import com.codebutler.farebot.transit.TransitRegion import com.codebutler.farebot.transit.Trip +import kotlin.time.Instant import com.codebutler.farebot.transit.seqgo.record.SeqGoBalanceRecord import com.codebutler.farebot.transit.seqgo.record.SeqGoRecord import com.codebutler.farebot.transit.seqgo.record.SeqGoTapRecord @@ -111,26 +113,33 @@ class SeqGoTransitFactory : TransitFactory { var i = 0 while (sortedTaps.size > i) { val tapOn = sortedTaps[i] - val tripBuilder = SeqGoTrip.builder() - - tripBuilder.journeyId(tapOn.journey) - tripBuilder.startTime(tapOn.timestamp) - tripBuilder.startStationId(tapOn.station) - tripBuilder.startStation(SeqGoUtil.getStation(tapOn.station)) - tripBuilder.mode(tapOn.mode) + var endTime: Instant? = null + var endStationId = 0 + var endStation: Station? = null if (sortedTaps.size > i + 1 && sortedTaps[i + 1].journey == tapOn.journey && sortedTaps[i + 1].mode == tapOn.mode ) { val tapOff = sortedTaps[i + 1] - tripBuilder.endTime(tapOff.timestamp) - tripBuilder.endStationId(tapOff.station) - tripBuilder.endStation(SeqGoUtil.getStation(tapOff.station)) + endTime = tapOff.timestamp + endStationId = tapOff.station + endStation = SeqGoUtil.getStation(tapOff.station) i++ } - trips.add(tripBuilder.build()) + trips.add( + SeqGoTrip( + journeyId = tapOn.journey, + modeValue = tapOn.mode, + startTime = tapOn.timestamp, + endTime = endTime, + startStationId = tapOn.station, + endStationId = endStationId, + startStationValue = SeqGoUtil.getStation(tapOn.station), + endStationValue = endStation, + ) + ) i++ } diff --git a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt index 18832ceca..1c6fa66e7 100644 --- a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt +++ b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt @@ -33,14 +33,14 @@ import kotlin.time.Instant * Represents trip events on Go Card. */ class SeqGoTrip( - private val journeyId: Int, - private val modeValue: Mode, - private val startTime: Instant?, - private val endTime: Instant?, - private val startStationId: Int, - private val endStationId: Int, - private val startStationValue: Station?, - private val endStationValue: Station?, + private val journeyId: Int = 0, + private val modeValue: Mode = Mode.OTHER, + private val startTime: Instant? = null, + private val endTime: Instant? = null, + private val startStationId: Int = 0, + private val endStationId: Int = 0, + private val startStationValue: Station? = null, + private val endStationValue: Station? = null, ) : Trip() { override val startTimestamp: Instant? get() = startTime @@ -75,46 +75,4 @@ class SeqGoTrip( fun getEndStationId(): Int = endStationId - class Builder { - private var journeyId: Int = 0 - private var mode: Mode = Mode.OTHER - private var startTime: Instant? = null - private var endTime: Instant? = null - private var startStationId: Int = 0 - private var endStationId: Int = 0 - private var startStation: Station? = null - private var endStation: Station? = null - - fun journeyId(journeyId: Int) = apply { this.journeyId = journeyId } - - fun mode(mode: Mode) = apply { this.mode = mode } - - fun startTime(startTime: Instant?) = apply { this.startTime = startTime } - - fun endTime(endTime: Instant?) = apply { this.endTime = endTime } - - fun startStationId(startStationId: Int) = apply { this.startStationId = startStationId } - - fun endStationId(endStationId: Int) = apply { this.endStationId = endStationId } - - fun startStation(station: Station?) = apply { this.startStation = station } - - fun endStation(station: Station?) = apply { this.endStation = station } - - fun build(): SeqGoTrip = - SeqGoTrip( - journeyId = journeyId, - modeValue = mode, - startTime = startTime, - endTime = endTime, - startStationId = startStationId, - endStationId = endStationId, - startStationValue = startStation, - endStationValue = endStation, - ) - } - - companion object { - fun builder(): Builder = Builder() - } } From 82ccef96cc050da8fdeddc7c6152456b4bc3ce7b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:19:33 -0800 Subject: [PATCH 5/9] refactor: remove unused Station.Builder Co-Authored-By: Claude Opus 4.6 --- .../com/codebutler/farebot/transit/Station.kt | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt b/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt index 01748f0b8..fb06c9954 100644 --- a/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt +++ b/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt @@ -104,68 +104,5 @@ data class Station( longitude = longitude?.toFloatOrNull(), ) - fun builder(): Builder = Builder() - } - - class Builder { - private var stationName: String? = null - private var shortStationName: String? = null - private var companyName: String? = null - private var lineNames: List = emptyList() - private var latitude: String? = null - private var longitude: String? = null - private var code: String? = null - private var abbreviation: String? = null - - fun stationName(stationName: String?): Builder { - this.stationName = stationName - return this - } - - fun shortStationName(shortStationName: String?): Builder { - this.shortStationName = shortStationName - return this - } - - fun companyName(companyName: String?): Builder { - this.companyName = companyName - return this - } - - fun lineNames(lineNames: List): Builder { - this.lineNames = lineNames - return this - } - - fun latitude(latitude: String?): Builder { - this.latitude = latitude - return this - } - - fun longitude(longitude: String?): Builder { - this.longitude = longitude - return this - } - - fun code(code: String?): Builder { - this.code = code - return this - } - - fun abbreviation(abbreviation: String?): Builder { - this.abbreviation = abbreviation - return this - } - - fun build(): Station = - Station( - stationName = stationName, - shortStationName = shortStationName ?: abbreviation, - companyName = companyName, - lineNames = lineNames, - latitude = latitude?.toFloatOrNull(), - longitude = longitude?.toFloatOrNull(), - humanReadableId = code, - ) } } From ab4c05eac7dfa6e8188e6bdd07daba8625a0c7df Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:21:05 -0800 Subject: [PATCH 6/9] refactor: convert card getAdvancedUi() from builder to DSL Co-Authored-By: Claude Opus 4.6 --- .../farebot/card/cepas/CEPASCard.kt | 78 +++---- .../farebot/card/classic/ClassicCard.kt | 41 ++-- .../farebot/card/desfire/DesfireCard.kt | 194 ++++++++---------- .../farebot/card/felica/FelicaCard.kt | 45 ++-- .../farebot/card/iso7816/ISO7816Card.kt | 72 ++++--- .../farebot/card/ksx6924/KSX6924PurseInfo.kt | 25 ++- .../farebot/card/ultralight/UltralightCard.kt | 22 +- .../farebot/card/vicinity/VicinityCard.kt | 27 +-- 8 files changed, 234 insertions(+), 270 deletions(-) diff --git a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt index 9867be372..be4d71398 100644 --- a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt +++ b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt @@ -24,6 +24,7 @@ package com.codebutler.farebot.card.cepas import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.CurrencyFormatter import com.codebutler.farebot.base.util.DateFormatStyle import com.codebutler.farebot.base.util.formatDate @@ -46,46 +47,47 @@ data class CEPASCard( fun getHistory(purse: Int): CEPASHistory? = histories[purse] - override suspend fun getAdvancedUi(): FareBotUiTree { - val cardUiBuilder = FareBotUiTree.builder() - - val pursesUiBuilder = cardUiBuilder.item().title("Purses") + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = "Purses" + for (purse in purses) { + item { + title = "Purse ID ${purse.id}" + item { title = "CEPAS Version"; value = purse.cepasVersion } + item { title = "Purse Status"; value = purse.purseStatus } + item { + title = "Purse Balance" + value = CurrencyFormatter.formatValue(purse.purseBalance / 100.0, "SGD") + } + item { + title = "Purse Creation Date" + value = formatDate(Instant.fromEpochMilliseconds(purse.purseCreationDate * 1000L), DateFormatStyle.LONG) + } + item { + title = "Purse Expiry Date" + value = formatDate(Instant.fromEpochMilliseconds(purse.purseExpiryDate * 1000L), DateFormatStyle.LONG) + } + item { title = "Autoload Amount"; value = purse.autoLoadAmount } + item { title = "CAN"; value = purse.can } + item { title = "CSN"; value = purse.csn } + } + } + } for (purse in purses) { - val purseUiBuilder = - pursesUiBuilder - .item() - .title("Purse ID ${purse.id}") - purseUiBuilder.item().title("CEPAS Version").value(purse.cepasVersion) - purseUiBuilder.item().title("Purse Status").value(purse.purseStatus) - purseUiBuilder - .item() - .title("Purse Balance") - .value(CurrencyFormatter.formatValue(purse.purseBalance / 100.0, "SGD")) - purseUiBuilder - .item() - .title("Purse Creation Date") - .value(formatDate(Instant.fromEpochMilliseconds(purse.purseCreationDate * 1000L), DateFormatStyle.LONG)) - purseUiBuilder - .item() - .title("Purse Expiry Date") - .value(formatDate(Instant.fromEpochMilliseconds(purse.purseExpiryDate * 1000L), DateFormatStyle.LONG)) - purseUiBuilder.item().title("Autoload Amount").value(purse.autoLoadAmount) - purseUiBuilder.item().title("CAN").value(purse.can) - purseUiBuilder.item().title("CSN").value(purse.csn) - - val transactionUiBuilder = cardUiBuilder.item().title("Last Transaction Information") - transactionUiBuilder.item().title("TRP").value(purse.lastTransactionTRP) - transactionUiBuilder.item().title("Credit TRP").value(purse.lastCreditTransactionTRP) - transactionUiBuilder.item().title("Credit Header").value(purse.lastCreditTransactionHeader) - transactionUiBuilder.item().title("Debit Options").value(purse.lastTransactionDebitOptionsByte) - - val otherUiBuilder = cardUiBuilder.item().title("Other Purse Information") - otherUiBuilder.item().title("Logfile Record Count").value(purse.logfileRecordCount) - otherUiBuilder.item().title("Issuer Data Length").value(purse.issuerDataLength) - otherUiBuilder.item().title("Issuer-specific Data").value(purse.issuerSpecificData) + item { + title = "Last Transaction Information" + item { title = "TRP"; value = purse.lastTransactionTRP } + item { title = "Credit TRP"; value = purse.lastCreditTransactionTRP } + item { title = "Credit Header"; value = purse.lastCreditTransactionHeader } + item { title = "Debit Options"; value = purse.lastTransactionDebitOptionsByte } + } + item { + title = "Other Purse Information" + item { title = "Logfile Record Count"; value = purse.logfileRecordCount } + item { title = "Issuer Data Length"; value = purse.issuerDataLength } + item { title = "Issuer-specific Data"; value = purse.issuerSpecificData } + } } - - return cardUiBuilder.build() } companion object { diff --git a/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt b/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt index 7d42963fe..d025aa89c 100644 --- a/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt +++ b/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt @@ -27,6 +27,7 @@ package com.codebutler.farebot.card.classic import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType @@ -56,48 +57,44 @@ class ClassicCard( return ClassicManufacturingInfo.parse(block0.data, tagId) } - override suspend fun getAdvancedUi(): FareBotUiTree { - val cardUiBuilder = FareBotUiTree.builder() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { for (sector in sectors) { val sectorIndexString = sector.index.toString(16) - val sectorUiBuilder = cardUiBuilder.item() when (sector) { is UnauthorizedClassicSector -> { - sectorUiBuilder.title( - FormattedString( + item { + title = FormattedString( Res.string.classic_unauthorized_sector_title_format, sectorIndexString, - ), - ) + ) + } } is InvalidClassicSector -> { - sectorUiBuilder.title( - FormattedString( + item { + title = FormattedString( Res.string.classic_invalid_sector_title_format, sectorIndexString, sector.error, - ), - ) + ) + } } else -> { val dataClassicSector = sector as DataClassicSector - sectorUiBuilder.title( - FormattedString(Res.string.classic_sector_title_format, sectorIndexString), - ) - for (block in dataClassicSector.blocks) { - sectorUiBuilder - .item() - .title( - FormattedString( + item { + title = FormattedString(Res.string.classic_sector_title_format, sectorIndexString) + for (block in dataClassicSector.blocks) { + item { + title = FormattedString( Res.string.classic_block_title_format, block.index.toString(), - ), - ).value(block.data) + ) + value = block.data + } + } } } } } - return cardUiBuilder.build() } companion object { diff --git a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt index f4a454b62..e6806a3ef 100644 --- a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt +++ b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt @@ -26,6 +26,7 @@ package com.codebutler.farebot.card.desfire import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType import kotlinx.serialization.Contextual @@ -44,122 +45,91 @@ data class DesfireCard( fun getApplication(appId: Int): DesfireApplication? = applications.firstOrNull { it.id == appId } - override suspend fun getAdvancedUi(): FareBotUiTree { - val cardUiBuilder = FareBotUiTree.builder() - val appsUiBuilder = cardUiBuilder.item().title("Applications") - for (app in applications) { - val appUiBuilder = - appsUiBuilder - .item() - .title("Application: 0x${app.id.toString(16)}") - val filesUiBuilder = appUiBuilder.item().title("Files") - for (file in app.files) { - val fileUiBuilder = - filesUiBuilder - .item() - .title("File: 0x${file.id.toString(16)}") - val fileSettings = file.fileSettings - if (fileSettings != null) { - val settingsUiBuilder = fileUiBuilder.item().title("Settings") - settingsUiBuilder - .item() - .title("Type") - .value(fileSettings.fileTypeName) - if (fileSettings is StandardDesfireFileSettings) { - settingsUiBuilder - .item() - .title("Size") - .value(fileSettings.fileSize) - } else if (fileSettings is RecordDesfireFileSettings) { - settingsUiBuilder - .item() - .title("Cur Records") - .value(fileSettings.curRecords) - settingsUiBuilder - .item() - .title("Max Records") - .value(fileSettings.maxRecords) - settingsUiBuilder - .item() - .title("Record Size") - .value(fileSettings.recordSize) - } else if (fileSettings is ValueDesfireFileSettings) { - settingsUiBuilder - .item() - .title("Range") - .value("${fileSettings.lowerLimit} - ${fileSettings.upperLimit}") - settingsUiBuilder - .item() - .title("Limited Credit") - .value( - "${fileSettings.limitedCreditValue} (${if (fileSettings.limitedCreditEnabled) "enabled" else "disabled"})", - ) + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = "Applications" + for (app in applications) { + item { + title = "Application: 0x${app.id.toString(16)}" + item { + title = "Files" + for (file in app.files) { + item { + title = "File: 0x${file.id.toString(16)}" + val fileSettings = file.fileSettings + if (fileSettings != null) { + item { + title = "Settings" + item { title = "Type"; value = fileSettings.fileTypeName } + if (fileSettings is StandardDesfireFileSettings) { + item { title = "Size"; value = fileSettings.fileSize } + } else if (fileSettings is RecordDesfireFileSettings) { + item { title = "Cur Records"; value = fileSettings.curRecords } + item { title = "Max Records"; value = fileSettings.maxRecords } + item { title = "Record Size"; value = fileSettings.recordSize } + } else if (fileSettings is ValueDesfireFileSettings) { + item { title = "Range"; value = "${fileSettings.lowerLimit} - ${fileSettings.upperLimit}" } + item { + title = "Limited Credit" + value = "${fileSettings.limitedCreditValue} (${if (fileSettings.limitedCreditEnabled) "enabled" else "disabled"})" + } + } + } + } + if (file is StandardDesfireFile) { + item { title = "Data"; value = file.data } + } else if (file is RecordDesfireFile) { + item { + title = "Records" + val records = file.records + for (i in records.indices) { + val record = records[i] + item { title = "Record $i"; value = record.data } + } + } + } else if (file is ValueDesfireFile) { + item { title = "Value"; value = file.value } + } else if (file is InvalidDesfireFile) { + item { title = "Error"; value = file.errorMessage } + } else if (file is UnauthorizedDesfireFile) { + item { title = "Error"; value = file.errorMessage } + } + } + } } } - if (file is StandardDesfireFile) { - fileUiBuilder - .item() - .title("Data") - .value(file.data) - } else if (file is RecordDesfireFile) { - val recordsUiBuilder = - fileUiBuilder - .item() - .title("Records") - val records = file.records - for (i in records.indices) { - val record = records[i] - recordsUiBuilder - .item() - .title("Record $i") - .value(record.data) - } - } else if (file is ValueDesfireFile) { - fileUiBuilder - .item() - .title("Value") - .value(file.value) - } else if (file is InvalidDesfireFile) { - fileUiBuilder - .item() - .title("Error") - .value(file.errorMessage) - } else if (file is UnauthorizedDesfireFile) { - fileUiBuilder - .item() - .title("Error") - .value(file.errorMessage) - } } } - - val manufacturingDataUiBuilder = cardUiBuilder.item().title("Manufacturing Data") - - val hwInfoUiBuilder = manufacturingDataUiBuilder.item().title("Hardware Information") - hwInfoUiBuilder.item().title("Vendor ID").value(manufacturingData.hwVendorID) - hwInfoUiBuilder.item().title("Type").value(manufacturingData.hwType) - hwInfoUiBuilder.item().title("Subtype").value(manufacturingData.hwSubType) - hwInfoUiBuilder.item().title("Major Version").value(manufacturingData.hwMajorVersion) - hwInfoUiBuilder.item().title("Minor Version").value(manufacturingData.hwMinorVersion) - hwInfoUiBuilder.item().title("Storage Size").value(manufacturingData.hwStorageSize) - hwInfoUiBuilder.item().title("Protocol").value(manufacturingData.hwProtocol) - - val swInfoUiBuilder = manufacturingDataUiBuilder.item().title("Software Information") - swInfoUiBuilder.item().title("Vendor ID").value(manufacturingData.swVendorID) - swInfoUiBuilder.item().title("Type").value(manufacturingData.swType) - swInfoUiBuilder.item().title("Subtype").value(manufacturingData.swSubType) - swInfoUiBuilder.item().title("Major Version").value(manufacturingData.swMajorVersion) - swInfoUiBuilder.item().title("Minor Version").value(manufacturingData.swMinorVersion) - swInfoUiBuilder.item().title("Storage Size").value(manufacturingData.swStorageSize) - swInfoUiBuilder.item().title("Protocol").value(manufacturingData.swProtocol) - - val generalInfoUiBuilder = manufacturingDataUiBuilder.item().title("General Information") - generalInfoUiBuilder.item().title("Serial Number").value(manufacturingData.uidHex) - generalInfoUiBuilder.item().title("Batch Number").value(manufacturingData.batchNoHex) - generalInfoUiBuilder.item().title("Week of Production").value(manufacturingData.weekProd.toString(16)) - generalInfoUiBuilder.item().title("Year of Production").value(manufacturingData.yearProd.toString(16)) - - return cardUiBuilder.build() + item { + title = "Manufacturing Data" + item { + title = "Hardware Information" + item { title = "Vendor ID"; value = manufacturingData.hwVendorID } + item { title = "Type"; value = manufacturingData.hwType } + item { title = "Subtype"; value = manufacturingData.hwSubType } + item { title = "Major Version"; value = manufacturingData.hwMajorVersion } + item { title = "Minor Version"; value = manufacturingData.hwMinorVersion } + item { title = "Storage Size"; value = manufacturingData.hwStorageSize } + item { title = "Protocol"; value = manufacturingData.hwProtocol } + } + item { + title = "Software Information" + item { title = "Vendor ID"; value = manufacturingData.swVendorID } + item { title = "Type"; value = manufacturingData.swType } + item { title = "Subtype"; value = manufacturingData.swSubType } + item { title = "Major Version"; value = manufacturingData.swMajorVersion } + item { title = "Minor Version"; value = manufacturingData.swMinorVersion } + item { title = "Storage Size"; value = manufacturingData.swStorageSize } + item { title = "Protocol"; value = manufacturingData.swProtocol } + } + item { + title = "General Information" + item { title = "Serial Number"; value = manufacturingData.uidHex } + item { title = "Batch Number"; value = manufacturingData.batchNoHex } + item { title = "Week of Production"; value = manufacturingData.weekProd.toString(16) } + item { title = "Year of Production"; value = manufacturingData.yearProd.toString(16) } + } + } } companion object { diff --git a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt index 427c9045d..ba21d9da6 100644 --- a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt +++ b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt @@ -27,6 +27,7 @@ package com.codebutler.farebot.card.felica import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType import kotlinx.serialization.Contextual @@ -50,34 +51,30 @@ data class FelicaCard( fun getSystem(systemCode: Int): FelicaSystem? = systemsByCode[systemCode] - override suspend fun getAdvancedUi(): FareBotUiTree { - val cardUiBuilder = FareBotUiTree.builder() - cardUiBuilder.item().title("IDm").value(idm) - cardUiBuilder.item().title("PMm").value(pmm) - val systemsUiBuilder = cardUiBuilder.item().title("Systems") - for (system in systems) { - val systemUiBuilder = - systemsUiBuilder - .item() - .title("System: ${system.code.toString(16)}") - for (service in system.services) { - val serviceUiBuilder = - systemUiBuilder - .item() - .title( - "Service: 0x${service.serviceCode.toString( + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = "IDm"; value = idm } + item { title = "PMm"; value = pmm } + item { + title = "Systems" + for (system in systems) { + item { + title = "System: ${system.code.toString(16)}" + for (service in system.services) { + item { + title = "Service: 0x${service.serviceCode.toString( 16, - )} (${FelicaUtils.getFriendlyServiceName(system.code, service.serviceCode)})", - ) - for (block in service.blocks) { - serviceUiBuilder - .item() - .title("Block ${block.address.toString().padStart(2, '0')}") - .value(block.data) + )} (${FelicaUtils.getFriendlyServiceName(system.code, service.serviceCode)})" + for (block in service.blocks) { + item { + title = "Block ${block.address.toString().padStart(2, '0')}" + value = block.data + } + } + } + } } } } - return cardUiBuilder.build() } companion object { diff --git a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt index 1d3f36247..28e5dfcd7 100644 --- a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt +++ b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt @@ -24,6 +24,7 @@ package com.codebutler.farebot.card.iso7816 import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType import kotlinx.serialization.Contextual @@ -51,45 +52,52 @@ data class ISO7816Card( fun getApplicationByName(appName: ByteArray): ISO7816Application? = applications.firstOrNull { it.appName?.toHexString() == appName.toHexString() } - override suspend fun getAdvancedUi(): FareBotUiTree { - val cardUiBuilder = FareBotUiTree.builder() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = "Applications" + for (app in applications) { + item { + val appNameStr = app.appName?.let { formatAID(it) } ?: "Unknown" + title = "Application: $appNameStr (${app.type})" - val appsUiBuilder = cardUiBuilder.item().title("Applications") - for (app in applications) { - val appUiBuilder = appsUiBuilder.item() - val appNameStr = app.appName?.let { formatAID(it) } ?: "Unknown" - appUiBuilder.title("Application: $appNameStr (${app.type})") - - // Show files - if (app.files.isNotEmpty()) { - val filesUiBuilder = appUiBuilder.item().title("Files") - for ((selector, file) in app.files) { - val fileUiBuilder = filesUiBuilder.item().title("File: $selector") - if (file.binaryData != null) { - fileUiBuilder.item().title("Binary Data").value(file.binaryData) - } - for ((index, record) in file.records.entries.sortedBy { it.key }) { - fileUiBuilder.item().title("Record $index").value(record) + // Show files + if (app.files.isNotEmpty()) { + item { + title = "Files" + for ((selector, file) in app.files) { + item { + title = "File: $selector" + if (file.binaryData != null) { + item { title = "Binary Data"; value = file.binaryData } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { title = "Record $index"; value = record } + } + } + } + } } - } - } - // Show SFI files - if (app.sfiFiles.isNotEmpty()) { - val sfiUiBuilder = appUiBuilder.item().title("SFI Files") - for ((sfi, file) in app.sfiFiles.entries.sortedBy { it.key }) { - val fileUiBuilder = sfiUiBuilder.item().title("SFI 0x${sfi.toString(16)}") - if (file.binaryData != null) { - fileUiBuilder.item().title("Binary Data").value(file.binaryData) - } - for ((index, record) in file.records.entries.sortedBy { it.key }) { - fileUiBuilder.item().title("Record $index").value(record) + // Show SFI files + if (app.sfiFiles.isNotEmpty()) { + item { + title = "SFI Files" + for ((sfi, file) in app.sfiFiles.entries.sortedBy { it.key }) { + item { + title = "SFI 0x${sfi.toString(16)}" + if (file.binaryData != null) { + item { title = "Binary Data"; value = file.binaryData } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { title = "Record $index"; value = record } + } + } + } + } } } } } - - return cardUiBuilder.build() } companion object { diff --git a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt index d9f7ab5d7..30a632b2d 100644 --- a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt +++ b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt @@ -25,6 +25,7 @@ package com.codebutler.farebot.card.ksx6924 import com.codebutler.farebot.base.ui.FareBotUiTree import com.codebutler.farebot.base.ui.ListItem +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.NumberUtils import com.codebutler.farebot.base.util.byteArrayToLong @@ -133,19 +134,17 @@ data class KSX6924PurseInfo( ListItem(Res.string.ksx6924_discount_type, resolver.resolveDisRate(disRate)), ) - suspend fun getAdvancedInfo(resolver: KSX6924PurseInfoResolver = KSX6924PurseInfoDefaultResolver): FareBotUiTree { - val b = FareBotUiTree.builder() - b.item().title(Res.string.ksx6924_crypto_algorithm).value(resolver.resolveCryptoAlgo(alg)) - b.item().title(Res.string.ksx6924_encryption_key_version).value(vk.hexString) - b.item().title(Res.string.ksx6924_auth_id).value(idtr.hexString) - b.item().title(Res.string.ksx6924_ticket_type).value(resolver.resolveUserCode(userCode)) - b.item().title(Res.string.ksx6924_max_balance).value(balMax.toString()) - b.item().title(Res.string.ksx6924_branch_code).value(bra.hexString) - b.item().title(Res.string.ksx6924_one_time_limit).value(mmax.toString()) - b.item().title(Res.string.ksx6924_mobile_carrier).value(resolver.resolveTCode(tcode)) - b.item().title(Res.string.ksx6924_financial_institution).value(resolver.resolveCCode(ccode)) - b.item().title(Res.string.ksx6924_rfu).value(rfu.hex()) - return b.build() + suspend fun getAdvancedInfo(resolver: KSX6924PurseInfoResolver = KSX6924PurseInfoDefaultResolver): FareBotUiTree = uiTree { + item { title = Res.string.ksx6924_crypto_algorithm; value = resolver.resolveCryptoAlgo(alg) } + item { title = Res.string.ksx6924_encryption_key_version; value = vk.hexString } + item { title = Res.string.ksx6924_auth_id; value = idtr.hexString } + item { title = Res.string.ksx6924_ticket_type; value = resolver.resolveUserCode(userCode) } + item { title = Res.string.ksx6924_max_balance; value = balMax.toString() } + item { title = Res.string.ksx6924_branch_code; value = bra.hexString } + item { title = Res.string.ksx6924_one_time_limit; value = mmax.toString() } + item { title = Res.string.ksx6924_mobile_carrier; value = resolver.resolveTCode(tcode) } + item { title = Res.string.ksx6924_financial_institution; value = resolver.resolveCCode(ccode) } + item { title = Res.string.ksx6924_rfu; value = rfu.hex() } } override fun equals(other: Any?): Boolean { diff --git a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt index b9a462f6b..acc904dc7 100644 --- a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt +++ b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt @@ -26,6 +26,7 @@ package com.codebutler.farebot.card.ultralight import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType @@ -68,19 +69,16 @@ data class UltralightCard( return result } - override suspend fun getAdvancedUi(): FareBotUiTree { - val builder = FareBotUiTree.builder() - val pagesBuilder = - builder - .item() - .title(Res.string.ultralight_pages) - for (page in pages) { - pagesBuilder - .item() - .title(FormattedString(Res.string.ultralight_page_title_format, page.index.toString())) - .value(page.data) + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { + title = Res.string.ultralight_pages + for (page in pages) { + item { + title = FormattedString(Res.string.ultralight_page_title_format, page.index.toString()) + value = page.data + } + } } - return builder.build() } /** diff --git a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt index acc2c9ec9..8dcba76c4 100644 --- a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt +++ b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.card.vicinity import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.card.Card import com.codebutler.farebot.card.CardType import kotlinx.serialization.Contextual @@ -77,27 +78,19 @@ data class VicinityCard( } @OptIn(ExperimentalStdlibApi::class) - override suspend fun getAdvancedUi(): FareBotUiTree { - val builder = FareBotUiTree.builder() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { if (sysInfo != null) { - builder - .item() - .title("System Info") - .value(sysInfo) + item { title = "System Info"; value = sysInfo } } - val pagesBuilder = builder.item().title("Pages") - for (page in pages) { - val pageBuilder = - pagesBuilder - .item() - .title("Page ${page.index}") - if (page.isUnauthorized) { - pageBuilder.value("Unauthorized") - } else { - pageBuilder.value(page.data) + item { + title = "Pages" + for (page in pages) { + item { + title = "Page ${page.index}" + value = if (page.isUnauthorized) "Unauthorized" else page.data + } } } - return builder.build() } companion object { From dd1f01c9526bfb15d942ed82cd42646959dfa961 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:25:00 -0800 Subject: [PATCH 7/9] refactor: convert transit getAdvancedUi() from builder to DSL Co-Authored-By: Claude Opus 4.6 --- .../lisboaviva/LisboaVivaTransitInfo.kt | 7 +++--- .../transit/charlie/CharlieCardTransitInfo.kt | 7 +++--- .../farebot/transit/hsl/HSLTransitInfo.kt | 13 ++++++----- .../NextfareUltralightTransitData.kt | 7 +++--- .../transit/octopus/OctopusTransitInfo.kt | 23 ++++++++----------- .../farebot/transit/ovc/OVChipIndex.kt | 15 ++++++------ .../farebot/transit/ovc/OVChipTransitInfo.kt | 15 ++++++------ .../transit/serialonly/HoloTransitInfo.kt | 7 +++--- .../transit/serialonly/StrelkaTransitInfo.kt | 7 +++--- .../smartrider/SmartRiderTransitInfo.kt | 20 ++++------------ .../transit/troika/TroikaHybridTransitInfo.kt | 21 +++++++---------- .../transit/umarsh/UmarshTransitInfo.kt | 9 ++++---- 12 files changed, 68 insertions(+), 83 deletions(-) diff --git a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt index bc26e985d..870952b58 100644 --- a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt +++ b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt @@ -21,6 +21,7 @@ package com.codebutler.farebot.transit.calypso.lisboaviva import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.FormattedString @@ -60,9 +61,9 @@ class LisboaVivaTransitInfo internal constructor( override suspend fun getAdvancedUi(): FareBotUiTree? { if (tagId == null) return null - val b = FareBotUiTree.builder() - b.item().title(Res.string.calypso_engraved_serial).value(tagId.toString()) - return b.build() + return uiTree { + item { title = Res.string.calypso_engraved_serial; value = tagId.toString() } + } } companion object { diff --git a/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt b/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt index e5bf707c1..1eaf25fdf 100644 --- a/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt +++ b/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.charlie import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.NumberUtils import com.codebutler.farebot.transit.Subscription @@ -88,9 +89,9 @@ class CharlieCardTransitInfo internal constructor( override suspend fun getAdvancedUi(): FareBotUiTree? { if (secondSerial == 0L || secondSerial == 0xffffffffL) return null - val b = FareBotUiTree.builder() - b.item().title(Res.string.charlie_2nd_card_number).value("A" + NumberUtils.zeroPad(secondSerial, 10)) - return b.build() + return uiTree { + item { title = Res.string.charlie_2nd_card_number; value = "A" + NumberUtils.zeroPad(secondSerial, 10) } + } } companion object { diff --git a/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt b/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt index b7245fb71..8fe907736 100644 --- a/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt +++ b/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt @@ -25,6 +25,7 @@ package com.codebutler.farebot.transit.hsl import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.transit.Subscription import com.codebutler.farebot.transit.TransitBalance @@ -51,12 +52,12 @@ class HSLTransitInfo( get() = TransitBalance(balance = TransitCurrency.EUR(mBalance)) override suspend fun getAdvancedUi(): FareBotUiTree? { - val b = FareBotUiTree.builder() - applicationVersion?.let { b.item().title(Res.string.hsl_application_version).value(it) } - applicationKeyVersion?.let { b.item().title(Res.string.hsl_application_key_version).value(it) } - platformType?.let { b.item().title(Res.string.hsl_platform_type).value(it) } - securityLevel?.let { b.item().title(Res.string.hsl_security_level).value(it) } - val tree = b.build() + val tree = uiTree { + applicationVersion?.let { item { title = Res.string.hsl_application_version; value = it } } + applicationKeyVersion?.let { item { title = Res.string.hsl_application_key_version; value = it } } + platformType?.let { item { title = Res.string.hsl_platform_type; value = it } } + securityLevel?.let { item { title = Res.string.hsl_security_level; value = it } } + } return if (tree.items.isEmpty()) null else tree } } diff --git a/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt b/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt index ad4e78dc0..3f52201dc 100644 --- a/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt +++ b/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.nextfareul import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.FormattedString @@ -102,10 +103,8 @@ abstract class NextfareUltralightTransitData : TransitInfo() { return items } - override suspend fun getAdvancedUi(): FareBotUiTree { - val b = FareBotUiTree.builder() - b.item().title(Res.string.nextfareul_machine_code).value(capsule.mMachineCode.toString(16)) - return b.build() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.nextfareul_machine_code; value = capsule.mMachineCode.toString(16) } } protected abstract fun makeCurrency(value: Int): TransitCurrency diff --git a/transit/octopus/src/commonMain/kotlin/com/codebutler/farebot/transit/octopus/OctopusTransitInfo.kt b/transit/octopus/src/commonMain/kotlin/com/codebutler/farebot/transit/octopus/OctopusTransitInfo.kt index ef7596872..0efff65d1 100644 --- a/transit/octopus/src/commonMain/kotlin/com/codebutler/farebot/transit/octopus/OctopusTransitInfo.kt +++ b/transit/octopus/src/commonMain/kotlin/com/codebutler/farebot/transit/octopus/OctopusTransitInfo.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.octopus import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.transit.TransitBalance import com.codebutler.farebot.transit.TransitCurrency @@ -87,20 +88,16 @@ class OctopusTransitInfo( } override suspend fun getAdvancedUi(): FareBotUiTree? { - // Dual-mode card, show the CNY balance here. val szt = shenzhenBalance - if (hasOctopus && szt != null) { - val uiBuilder = FareBotUiTree.builder() - val apbUiBuilder = - uiBuilder - .item() - .title(Res.string.octopus_alternate_purse_balances) - apbUiBuilder.item( - Res.string.octopus_szt, - TransitCurrency.CNY(szt).formatCurrencyString(isBalance = true), - ) - return uiBuilder.build() + if (!hasOctopus || szt == null) return null + return uiTree { + item { + title = Res.string.octopus_alternate_purse_balances + item { + title = Res.string.octopus_szt + value = TransitCurrency.CNY(szt).formatCurrencyString(isBalance = true) + } + } } - return null } } diff --git a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt index 4f7617291..d6b08cc2c 100644 --- a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt +++ b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt @@ -24,6 +24,7 @@ package com.codebutler.farebot.transit.ovc import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.transit.en1545.getBitsFromBuffer import kotlinx.serialization.Serializable @@ -37,13 +38,13 @@ data class OVChipIndex internal constructor( val recentCreditSlot: Boolean, // Most recent credit index slot (0xF90(false) or 0xFA0(true)) val subscriptionIndex: List, ) { - fun addAdvancedItems(b: FareBotUiTree.Item.Builder) { - b.item("Transaction Slot", if (recentTransactionSlot) "B" else "A") - b.item("Info Slot", if (recentInfoSlot) "B" else "A") - b.item("Subscription Slot", if (recentSubscriptionSlot) "B" else "A") - b.item("Travelhistory Slot", if (recentTravelhistorySlot) "B" else "A") - b.item("Credit Slot", if (recentCreditSlot) "B" else "A") - } + fun advancedItems(): List = listOf( + FareBotUiTree.Item(title = FormattedString("Transaction Slot"), value = if (recentTransactionSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Info Slot"), value = if (recentInfoSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Subscription Slot"), value = if (recentSubscriptionSlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Travelhistory Slot"), value = if (recentTravelhistorySlot) "B" else "A"), + FareBotUiTree.Item(title = FormattedString("Credit Slot"), value = if (recentCreditSlot) "B" else "A"), + ) companion object { fun parse(data: ByteArray): OVChipIndex { diff --git a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt index c3bbb75a7..02c5494c6 100644 --- a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt +++ b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt @@ -25,6 +25,7 @@ package com.codebutler.farebot.transit.ovc import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.HeaderListItem import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface @@ -148,13 +149,13 @@ class OVChipTransitInfo( return li } - override suspend fun getAdvancedUi(): FareBotUiTree { - val b = FareBotUiTree.builder() - b.item().title("Credit Slot ID").value(creditSlotId.toString()) - b.item().title("Last Credit ID").value(creditId.toString()) - val slotsItem = b.item().title("Recent Slots") - index.addAdvancedItems(slotsItem) - return b.build() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = "Credit Slot ID"; value = creditSlotId.toString() } + item { title = "Last Credit ID"; value = creditId.toString() } + item { + title = "Recent Slots" + addChildren(index.advancedItems()) + } } companion object { diff --git a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt index cbe18f3ad..35e05b1e1 100644 --- a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt +++ b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt @@ -11,6 +11,7 @@ package com.codebutler.farebot.transit.serialonly import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.FormattedString @@ -43,10 +44,8 @@ class HoloTransitInfo( ), ) - override suspend fun getAdvancedUi(): FareBotUiTree { - val b = FareBotUiTree.builder() - b.item().title(Res.string.manufacture_id).value(mManufacturingId) - return b.build() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.manufacture_id; value = mManufacturingId } } override val reason get() = Reason.NOT_STORED diff --git a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt index 6a1e577eb..cf3aca3fe 100644 --- a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt +++ b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt @@ -11,6 +11,7 @@ package com.codebutler.farebot.transit.serialonly import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import farebot.transit.serialonly.generated.resources.Res import farebot.transit.serialonly.generated.resources.card_name_strelka @@ -19,10 +20,8 @@ import farebot.transit.serialonly.generated.resources.strelka_long_serial class StrelkaTransitInfo( private val mSerial: String, ) : SerialOnlyTransitInfo() { - override suspend fun getAdvancedUi(): FareBotUiTree { - val b = FareBotUiTree.builder() - b.item().title(Res.string.strelka_long_serial).value(mSerial) - return b.build() + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.strelka_long_serial; value = mSerial } } override val reason get() = Reason.MORE_RESEARCH_NEEDED diff --git a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt index 34ce3d77a..47054ac0c 100644 --- a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt +++ b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.smartrider import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.transit.Subscription import com.codebutler.farebot.transit.TransitBalance @@ -101,22 +102,11 @@ class SmartRiderTransitInfo( override val subscriptions: List? = null - override suspend fun getAdvancedUi(): FareBotUiTree? { - val uiBuilder = FareBotUiTree.builder() - uiBuilder - .item() - .title(Res.string.smartrider_ticket_type) - .value(mTokenType.toString()) + override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { + item { title = Res.string.smartrider_ticket_type; value = mTokenType.toString() } if (mSmartRiderType == SmartRiderType.SMARTRIDER) { - uiBuilder - .item() - .title(Res.string.smartrider_autoload_threshold) - .value(TransitCurrency.AUD(mAutoloadThreshold).formatCurrencyString(true)) - uiBuilder - .item() - .title(Res.string.smartrider_autoload_value) - .value(TransitCurrency.AUD(mAutoloadValue).formatCurrencyString(true)) + item { title = Res.string.smartrider_autoload_threshold; value = TransitCurrency.AUD(mAutoloadThreshold).formatCurrencyString(true) } + item { title = Res.string.smartrider_autoload_value; value = TransitCurrency.AUD(mAutoloadValue).formatCurrencyString(true) } } - return uiBuilder.build() } } diff --git a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt index 6a184875e..9a07443bb 100644 --- a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt +++ b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt @@ -106,19 +106,14 @@ class TroikaHybridTransitInfo( get() = troika.warning override suspend fun getAdvancedUi(): FareBotUiTree? { - val trees = - listOfNotNull( - troika.getAdvancedUi(), - podorozhnik?.getAdvancedUi(), - strelka?.getAdvancedUi(), - ) + val trees = listOfNotNull( + troika.getAdvancedUi(), + podorozhnik?.getAdvancedUi(), + strelka?.getAdvancedUi(), + ) if (trees.isEmpty()) return null - val b = FareBotUiTree.builder() - for (tree in trees) { - for (item in tree.items) { - b.item().title(item.title).value(item.value) - } - } - return b.build() + return FareBotUiTree(items = trees.flatMap { tree -> + tree.items.map { FareBotUiTree.Item(title = it.title, value = it.value) } + }) } } diff --git a/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt b/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt index f46cb8fb3..12cd33e2b 100644 --- a/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt +++ b/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt @@ -23,6 +23,7 @@ package com.codebutler.farebot.transit.umarsh import com.codebutler.farebot.base.ui.FareBotUiTree +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface import com.codebutler.farebot.base.util.FormattedString @@ -82,11 +83,11 @@ class UmarshTransitInfo( override suspend fun getAdvancedUi(): FareBotUiTree? { val rubSectors = sectors.filter { it.denomination == UmarshDenomination.RUB } if (rubSectors.isEmpty()) return null - val b = FareBotUiTree.builder() - for (sec in rubSectors) { - b.item().title(Res.string.umarsh_machine_id).value(sec.machineId.toString()) + return uiTree { + for (sec in rubSectors) { + item { title = Res.string.umarsh_machine_id; value = sec.machineId.toString() } + } } - return b.build() } override val trips: List? From 251b8e5d8d447e158edd8994e6854c4a97e528b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 14:29:42 -0800 Subject: [PATCH 8/9] fix: replace remaining Station.Builder() calls with constructor Missed call sites in clipper, easycard, ezlink, kmt, orca, podorozhnik, seqgo, smartrider, suica, tfi-leap, and troika modules. Co-Authored-By: Claude Opus 4.6 --- .../farebot/transit/clipper/ClipperData.kt | 17 ++++++----- .../easycard/EasyCardTransitFactory.kt | 17 ++++++----- .../farebot/transit/ezlink/EZLinkData.kt | 19 ++++++------- .../codebutler/farebot/transit/kmt/KMTData.kt | 17 ++++++----- .../farebot/transit/orca/OrcaTransaction.kt | 17 ++++++----- .../transit/podorozhnik/PodorozhnikTopup.kt | 17 ++++++----- .../transit/podorozhnik/PodorozhnikTrip.kt | 17 ++++++----- .../farebot/transit/seqgo/SeqGoUtil.kt | 11 ++++---- .../transit/smartrider/SmartRiderTagRecord.kt | 17 ++++++----- .../farebot/transit/suica/SuicaUtil.kt | 28 +++++++++---------- .../farebot/transit/tfileap/LeapTrip.kt | 17 ++++++----- .../troika/TroikaUltralightTransitFactory.kt | 17 ++++++----- 12 files changed, 99 insertions(+), 112 deletions(-) diff --git a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperData.kt b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperData.kt index 2e8b1a93a..8d6212919 100644 --- a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperData.kt +++ b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperData.kt @@ -70,15 +70,14 @@ internal object ClipperData { val id = (agency shl 16) or stationId val result = MdstStationLookup.getStation(CLIPPER_STR, id) if (result != null) { - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } if (agency == AGENCY_GGT || diff --git a/transit/easycard/src/commonMain/kotlin/com/codebutler/farebot/transit/easycard/EasyCardTransitFactory.kt b/transit/easycard/src/commonMain/kotlin/com/codebutler/farebot/transit/easycard/EasyCardTransitFactory.kt index 34401b2d2..c97d790e2 100644 --- a/transit/easycard/src/commonMain/kotlin/com/codebutler/farebot/transit/easycard/EasyCardTransitFactory.kt +++ b/transit/easycard/src/commonMain/kotlin/com/codebutler/farebot/transit/easycard/EasyCardTransitFactory.kt @@ -112,15 +112,14 @@ class EasyCardTransitFactory : TransitFactory // Try MDST database first val result = MdstStationLookup.getStation(EASYCARD_STR, stationId) if (result != null) { - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } // Fallback to hardcoded map val name = EasyCardStations[stationId] diff --git a/transit/ezlink/src/commonMain/kotlin/com/codebutler/farebot/transit/ezlink/EZLinkData.kt b/transit/ezlink/src/commonMain/kotlin/com/codebutler/farebot/transit/ezlink/EZLinkData.kt index 95440c05c..e51265587 100644 --- a/transit/ezlink/src/commonMain/kotlin/com/codebutler/farebot/transit/ezlink/EZLinkData.kt +++ b/transit/ezlink/src/commonMain/kotlin/com/codebutler/farebot/transit/ezlink/EZLinkData.kt @@ -52,16 +52,15 @@ internal object EZLinkData { val result = MdstStationLookup.getStation(EZLINK_STR, stationId) if (result != null) { - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .code(code) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + humanReadableId = code, + ) } return Station.unknown(code) diff --git a/transit/kmt/src/commonMain/kotlin/com/codebutler/farebot/transit/kmt/KMTData.kt b/transit/kmt/src/commonMain/kotlin/com/codebutler/farebot/transit/kmt/KMTData.kt index d4062fe59..f0809d1dd 100644 --- a/transit/kmt/src/commonMain/kotlin/com/codebutler/farebot/transit/kmt/KMTData.kt +++ b/transit/kmt/src/commonMain/kotlin/com/codebutler/farebot/transit/kmt/KMTData.kt @@ -92,15 +92,14 @@ internal object KMTData { // Try MDST database first val result = MdstStationLookup.getStation(KMT_STR, code) if (result != null) { - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } // Fallback to hardcoded map return KCI_STATIONS[code] diff --git a/transit/orca/src/commonMain/kotlin/com/codebutler/farebot/transit/orca/OrcaTransaction.kt b/transit/orca/src/commonMain/kotlin/com/codebutler/farebot/transit/orca/OrcaTransaction.kt index 685c674b0..c8a11edbf 100644 --- a/transit/orca/src/commonMain/kotlin/com/codebutler/farebot/transit/orca/OrcaTransaction.kt +++ b/transit/orca/src/commonMain/kotlin/com/codebutler/farebot/transit/orca/OrcaTransaction.kt @@ -257,15 +257,14 @@ class OrcaTransaction( stationId: Int, ): Station? { val result = MdstStationLookup.getStation(dbName, stationId) ?: return null - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } companion object { diff --git a/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTopup.kt b/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTopup.kt index fd01887c1..465445af8 100644 --- a/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTopup.kt +++ b/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTopup.kt @@ -73,14 +73,13 @@ internal class PodorozhnikTopup( stationId: Int, ): Station? { val result = MdstStationLookup.getStation(dbName, stationId) ?: return null - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } } diff --git a/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTrip.kt b/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTrip.kt index 8dd68d7a5..cadde325c 100644 --- a/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTrip.kt +++ b/transit/podorozhnik/src/commonMain/kotlin/com/codebutler/farebot/transit/podorozhnik/PodorozhnikTrip.kt @@ -102,15 +102,14 @@ internal class PodorozhnikTrip( stationId: Int, ): Station? { val result = MdstStationLookup.getStation(dbName, stationId) ?: return null - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } companion object { diff --git a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoUtil.kt b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoUtil.kt index e4080fb97..92c9f3487 100644 --- a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoUtil.kt +++ b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoUtil.kt @@ -85,11 +85,10 @@ object SeqGoUtil { val result = MdstStationLookup.getStation(SEQ_GO_STR, stationId) ?: return null - return Station - .builder() - .stationName(result.stationName) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } } diff --git a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTagRecord.kt b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTagRecord.kt index 486939f89..924367282 100644 --- a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTagRecord.kt +++ b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTagRecord.kt @@ -122,15 +122,14 @@ class SmartRiderTagRecord( stationId: Int, ): Station? { val result = MdstStationLookup.getStation(dbName, stationId) ?: return null - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } companion object { diff --git a/transit/suica/src/commonMain/kotlin/com/codebutler/farebot/transit/suica/SuicaUtil.kt b/transit/suica/src/commonMain/kotlin/com/codebutler/farebot/transit/suica/SuicaUtil.kt index 302aaf5bf..e73bca229 100644 --- a/transit/suica/src/commonMain/kotlin/com/codebutler/farebot/transit/suica/SuicaUtil.kt +++ b/transit/suica/src/commonMain/kotlin/com/codebutler/farebot/transit/suica/SuicaUtil.kt @@ -496,13 +496,12 @@ internal object SuicaUtil { val result = MdstStationLookup.getStation(SUICA_BUS_STR, stationId) if (result != null) { - return Station - .builder() - .companyName(result.companyName) - .stationName(result.stationName) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + companyName = result.companyName, + stationName = result.stationName, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } // Return unknown station with formatted ID @@ -526,14 +525,13 @@ internal object SuicaUtil { val result = MdstStationLookup.getStation(SUICA_RAIL_STR, stationId) if (result != null) { - return Station - .builder() - .companyName(result.companyName) - .lineNames(result.lineNames) - .stationName(result.stationName) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + companyName = result.companyName, + lineNames = result.lineNames, + stationName = result.stationName, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } // Return unknown station with formatted ID diff --git a/transit/tfi-leap/src/commonMain/kotlin/com/codebutler/farebot/transit/tfileap/LeapTrip.kt b/transit/tfi-leap/src/commonMain/kotlin/com/codebutler/farebot/transit/tfileap/LeapTrip.kt index 20e8c1841..8762c38c2 100644 --- a/transit/tfi-leap/src/commonMain/kotlin/com/codebutler/farebot/transit/tfileap/LeapTrip.kt +++ b/transit/tfi-leap/src/commonMain/kotlin/com/codebutler/farebot/transit/tfileap/LeapTrip.kt @@ -96,15 +96,14 @@ class LeapTrip internal constructor( private fun lookupStation(stationId: Int): Station? { val result = MdstStationLookup.getStation(LEAP_STR, stationId) ?: return null - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } companion object { diff --git a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaUltralightTransitFactory.kt b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaUltralightTransitFactory.kt index 8f2f2d45e..5c4011daf 100644 --- a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaUltralightTransitFactory.kt +++ b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaUltralightTransitFactory.kt @@ -615,15 +615,14 @@ private class TroikaTrip( if (validator == null || validator == 0) return null val result = MdstStationLookup.getStation(TROIKA_STR, validator) if (result != null) { - return Station - .Builder() - .stationName(result.stationName) - .shortStationName(result.shortStationName) - .companyName(result.companyName) - .lineNames(result.lineNames) - .latitude(if (result.hasLocation) result.latitude.toString() else null) - .longitude(if (result.hasLocation) result.longitude.toString() else null) - .build() + return Station( + stationName = result.stationName, + shortStationName = result.shortStationName, + companyName = result.companyName, + lineNames = result.lineNames, + latitude = if (result.hasLocation) result.latitude else null, + longitude = if (result.hasLocation) result.longitude else null, + ) } return Station.unknown(validator.toString()) } From 81224629202a25f839750f22d08b9ac88548268e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 15:32:08 -0800 Subject: [PATCH 9/9] style: apply ktlintFormat Co-Authored-By: Claude Opus 4.6 --- .../farebot/base/ui/UiTreeBuilder.kt | 22 +- .../farebot/card/cepas/CEPASCard.kt | 107 +++++--- .../farebot/card/classic/ClassicCard.kt | 66 ++--- .../farebot/card/desfire/DesfireCard.kt | 232 ++++++++++++------ .../farebot/card/felica/FelicaCard.kt | 43 ++-- .../farebot/card/iso7816/ISO7816Card.kt | 79 +++--- .../farebot/card/ksx6924/KSX6924PurseInfo.kt | 55 ++++- .../farebot/card/ultralight/UltralightCard.kt | 17 +- .../farebot/card/vicinity/VicinityCard.kt | 24 +- .../lisboaviva/LisboaVivaTransitInfo.kt | 7 +- .../transit/charlie/CharlieCardTransitInfo.kt | 5 +- .../farebot/transit/clipper/ClipperTrip.kt | 1 - .../farebot/transit/hsl/HSLTransitInfo.kt | 33 ++- .../NextfareUltralightTransitData.kt | 12 +- .../farebot/transit/ovc/OVChipIndex.kt | 24 +- .../farebot/transit/ovc/OVChipTransitInfo.kt | 23 +- .../transit/seqgo/SeqGoTransitFactory.kt | 4 +- .../farebot/transit/seqgo/SeqGoTrip.kt | 1 - .../transit/serialonly/HoloTransitInfo.kt | 12 +- .../transit/serialonly/StrelkaTransitInfo.kt | 10 +- .../smartrider/SmartRiderTransitInfo.kt | 24 +- .../com/codebutler/farebot/transit/Station.kt | 1 - .../transit/troika/TroikaHybridTransitInfo.kt | 20 +- .../transit/umarsh/UmarshTransitInfo.kt | 7 +- 24 files changed, 549 insertions(+), 280 deletions(-) diff --git a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt index d6413f60c..4666798a7 100644 --- a/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt +++ b/base/src/commonMain/kotlin/com/codebutler/farebot/base/ui/UiTreeBuilder.kt @@ -51,11 +51,12 @@ class ItemScope { var title: Any? get() = _title set(value) { - _title = when (value) { - is FormattedString -> value - is StringResource -> FormattedString(value) - else -> FormattedString(value.toString()) - } + _title = + when (value) { + is FormattedString -> value + is StringResource -> FormattedString(value) + else -> FormattedString(value.toString()) + } } var value: Any? = null @@ -72,9 +73,10 @@ class ItemScope { children.addAll(items) } - internal fun build(): FareBotUiTree.Item = FareBotUiTree.Item( - title = _title, - value = value, - children = children.toList(), - ) + internal fun build(): FareBotUiTree.Item = + FareBotUiTree.Item( + title = _title, + value = value, + children = children.toList(), + ) } diff --git a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt index be4d71398..986adb263 100644 --- a/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt +++ b/card/cepas/src/commonMain/kotlin/com/codebutler/farebot/card/cepas/CEPASCard.kt @@ -47,48 +47,93 @@ data class CEPASCard( fun getHistory(purse: Int): CEPASHistory? = histories[purse] - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { - title = "Purses" + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = "Purses" + for (purse in purses) { + item { + title = "Purse ID ${purse.id}" + item { + title = "CEPAS Version" + value = purse.cepasVersion + } + item { + title = "Purse Status" + value = purse.purseStatus + } + item { + title = "Purse Balance" + value = CurrencyFormatter.formatValue(purse.purseBalance / 100.0, "SGD") + } + item { + title = "Purse Creation Date" + value = + formatDate( + Instant.fromEpochMilliseconds(purse.purseCreationDate * 1000L), + DateFormatStyle.LONG, + ) + } + item { + title = "Purse Expiry Date" + value = + formatDate( + Instant.fromEpochMilliseconds(purse.purseExpiryDate * 1000L), + DateFormatStyle.LONG, + ) + } + item { + title = "Autoload Amount" + value = purse.autoLoadAmount + } + item { + title = "CAN" + value = purse.can + } + item { + title = "CSN" + value = purse.csn + } + } + } + } for (purse in purses) { item { - title = "Purse ID ${purse.id}" - item { title = "CEPAS Version"; value = purse.cepasVersion } - item { title = "Purse Status"; value = purse.purseStatus } + title = "Last Transaction Information" item { - title = "Purse Balance" - value = CurrencyFormatter.formatValue(purse.purseBalance / 100.0, "SGD") + title = "TRP" + value = purse.lastTransactionTRP } item { - title = "Purse Creation Date" - value = formatDate(Instant.fromEpochMilliseconds(purse.purseCreationDate * 1000L), DateFormatStyle.LONG) + title = "Credit TRP" + value = purse.lastCreditTransactionTRP } item { - title = "Purse Expiry Date" - value = formatDate(Instant.fromEpochMilliseconds(purse.purseExpiryDate * 1000L), DateFormatStyle.LONG) + title = "Credit Header" + value = purse.lastCreditTransactionHeader + } + item { + title = "Debit Options" + value = purse.lastTransactionDebitOptionsByte + } + } + item { + title = "Other Purse Information" + item { + title = "Logfile Record Count" + value = purse.logfileRecordCount + } + item { + title = "Issuer Data Length" + value = purse.issuerDataLength + } + item { + title = "Issuer-specific Data" + value = purse.issuerSpecificData } - item { title = "Autoload Amount"; value = purse.autoLoadAmount } - item { title = "CAN"; value = purse.can } - item { title = "CSN"; value = purse.csn } } } } - for (purse in purses) { - item { - title = "Last Transaction Information" - item { title = "TRP"; value = purse.lastTransactionTRP } - item { title = "Credit TRP"; value = purse.lastCreditTransactionTRP } - item { title = "Credit Header"; value = purse.lastCreditTransactionHeader } - item { title = "Debit Options"; value = purse.lastTransactionDebitOptionsByte } - } - item { - title = "Other Purse Information" - item { title = "Logfile Record Count"; value = purse.logfileRecordCount } - item { title = "Issuer Data Length"; value = purse.issuerDataLength } - item { title = "Issuer-specific Data"; value = purse.issuerSpecificData } - } - } - } companion object { fun create( diff --git a/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt b/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt index d025aa89c..6ee6c21cb 100644 --- a/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt +++ b/card/classic/src/commonMain/kotlin/com/codebutler/farebot/card/classic/ClassicCard.kt @@ -57,45 +57,49 @@ class ClassicCard( return ClassicManufacturingInfo.parse(block0.data, tagId) } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - for (sector in sectors) { - val sectorIndexString = sector.index.toString(16) - when (sector) { - is UnauthorizedClassicSector -> { - item { - title = FormattedString( - Res.string.classic_unauthorized_sector_title_format, - sectorIndexString, - ) - } - } - is InvalidClassicSector -> { - item { - title = FormattedString( - Res.string.classic_invalid_sector_title_format, - sectorIndexString, - sector.error, - ) + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + for (sector in sectors) { + val sectorIndexString = sector.index.toString(16) + when (sector) { + is UnauthorizedClassicSector -> { + item { + title = + FormattedString( + Res.string.classic_unauthorized_sector_title_format, + sectorIndexString, + ) + } } - } - else -> { - val dataClassicSector = sector as DataClassicSector - item { - title = FormattedString(Res.string.classic_sector_title_format, sectorIndexString) - for (block in dataClassicSector.blocks) { - item { - title = FormattedString( - Res.string.classic_block_title_format, - block.index.toString(), + is InvalidClassicSector -> { + item { + title = + FormattedString( + Res.string.classic_invalid_sector_title_format, + sectorIndexString, + sector.error, ) - value = block.data + } + } + else -> { + val dataClassicSector = sector as DataClassicSector + item { + title = FormattedString(Res.string.classic_sector_title_format, sectorIndexString) + for (block in dataClassicSector.blocks) { + item { + title = + FormattedString( + Res.string.classic_block_title_format, + block.index.toString(), + ) + value = block.data + } } } } } } } - } companion object { fun create( diff --git a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt index e6806a3ef..cda93ee3a 100644 --- a/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt +++ b/card/desfire/src/commonMain/kotlin/com/codebutler/farebot/card/desfire/DesfireCard.kt @@ -45,92 +45,182 @@ data class DesfireCard( fun getApplication(appId: Int): DesfireApplication? = applications.firstOrNull { it.id == appId } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { - title = "Applications" - for (app in applications) { - item { - title = "Application: 0x${app.id.toString(16)}" - item { - title = "Files" - for (file in app.files) { - item { - title = "File: 0x${file.id.toString(16)}" - val fileSettings = file.fileSettings - if (fileSettings != null) { - item { - title = "Settings" - item { title = "Type"; value = fileSettings.fileTypeName } - if (fileSettings is StandardDesfireFileSettings) { - item { title = "Size"; value = fileSettings.fileSize } - } else if (fileSettings is RecordDesfireFileSettings) { - item { title = "Cur Records"; value = fileSettings.curRecords } - item { title = "Max Records"; value = fileSettings.maxRecords } - item { title = "Record Size"; value = fileSettings.recordSize } - } else if (fileSettings is ValueDesfireFileSettings) { - item { title = "Range"; value = "${fileSettings.lowerLimit} - ${fileSettings.upperLimit}" } + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = "Applications" + for (app in applications) { + item { + title = "Application: 0x${app.id.toString(16)}" + item { + title = "Files" + for (file in app.files) { + item { + title = "File: 0x${file.id.toString(16)}" + val fileSettings = file.fileSettings + if (fileSettings != null) { + item { + title = "Settings" item { - title = "Limited Credit" - value = "${fileSettings.limitedCreditValue} (${if (fileSettings.limitedCreditEnabled) "enabled" else "disabled"})" + title = "Type" + value = fileSettings.fileTypeName + } + if (fileSettings is StandardDesfireFileSettings) { + item { + title = "Size" + value = fileSettings.fileSize + } + } else if (fileSettings is RecordDesfireFileSettings) { + item { + title = "Cur Records" + value = fileSettings.curRecords + } + item { + title = "Max Records" + value = fileSettings.maxRecords + } + item { + title = "Record Size" + value = fileSettings.recordSize + } + } else if (fileSettings is ValueDesfireFileSettings) { + item { + title = "Range" + value = + "${fileSettings.lowerLimit} - ${fileSettings.upperLimit}" + } + item { + title = "Limited Credit" + value = + "${fileSettings.limitedCreditValue} (${if (fileSettings.limitedCreditEnabled) "enabled" else "disabled"})" + } } } } - } - if (file is StandardDesfireFile) { - item { title = "Data"; value = file.data } - } else if (file is RecordDesfireFile) { - item { - title = "Records" - val records = file.records - for (i in records.indices) { - val record = records[i] - item { title = "Record $i"; value = record.data } + if (file is StandardDesfireFile) { + item { + title = "Data" + value = file.data + } + } else if (file is RecordDesfireFile) { + item { + title = "Records" + val records = file.records + for (i in records.indices) { + val record = records[i] + item { + title = "Record $i" + value = record.data + } + } + } + } else if (file is ValueDesfireFile) { + item { + title = "Value" + value = file.value + } + } else if (file is InvalidDesfireFile) { + item { + title = "Error" + value = file.errorMessage + } + } else if (file is UnauthorizedDesfireFile) { + item { + title = "Error" + value = file.errorMessage } } - } else if (file is ValueDesfireFile) { - item { title = "Value"; value = file.value } - } else if (file is InvalidDesfireFile) { - item { title = "Error"; value = file.errorMessage } - } else if (file is UnauthorizedDesfireFile) { - item { title = "Error"; value = file.errorMessage } } } } } } } - } - item { - title = "Manufacturing Data" - item { - title = "Hardware Information" - item { title = "Vendor ID"; value = manufacturingData.hwVendorID } - item { title = "Type"; value = manufacturingData.hwType } - item { title = "Subtype"; value = manufacturingData.hwSubType } - item { title = "Major Version"; value = manufacturingData.hwMajorVersion } - item { title = "Minor Version"; value = manufacturingData.hwMinorVersion } - item { title = "Storage Size"; value = manufacturingData.hwStorageSize } - item { title = "Protocol"; value = manufacturingData.hwProtocol } - } - item { - title = "Software Information" - item { title = "Vendor ID"; value = manufacturingData.swVendorID } - item { title = "Type"; value = manufacturingData.swType } - item { title = "Subtype"; value = manufacturingData.swSubType } - item { title = "Major Version"; value = manufacturingData.swMajorVersion } - item { title = "Minor Version"; value = manufacturingData.swMinorVersion } - item { title = "Storage Size"; value = manufacturingData.swStorageSize } - item { title = "Protocol"; value = manufacturingData.swProtocol } - } item { - title = "General Information" - item { title = "Serial Number"; value = manufacturingData.uidHex } - item { title = "Batch Number"; value = manufacturingData.batchNoHex } - item { title = "Week of Production"; value = manufacturingData.weekProd.toString(16) } - item { title = "Year of Production"; value = manufacturingData.yearProd.toString(16) } + title = "Manufacturing Data" + item { + title = "Hardware Information" + item { + title = "Vendor ID" + value = manufacturingData.hwVendorID + } + item { + title = "Type" + value = manufacturingData.hwType + } + item { + title = "Subtype" + value = manufacturingData.hwSubType + } + item { + title = "Major Version" + value = manufacturingData.hwMajorVersion + } + item { + title = "Minor Version" + value = manufacturingData.hwMinorVersion + } + item { + title = "Storage Size" + value = manufacturingData.hwStorageSize + } + item { + title = "Protocol" + value = manufacturingData.hwProtocol + } + } + item { + title = "Software Information" + item { + title = "Vendor ID" + value = manufacturingData.swVendorID + } + item { + title = "Type" + value = manufacturingData.swType + } + item { + title = "Subtype" + value = manufacturingData.swSubType + } + item { + title = "Major Version" + value = manufacturingData.swMajorVersion + } + item { + title = "Minor Version" + value = manufacturingData.swMinorVersion + } + item { + title = "Storage Size" + value = manufacturingData.swStorageSize + } + item { + title = "Protocol" + value = manufacturingData.swProtocol + } + } + item { + title = "General Information" + item { + title = "Serial Number" + value = manufacturingData.uidHex + } + item { + title = "Batch Number" + value = manufacturingData.batchNoHex + } + item { + title = "Week of Production" + value = manufacturingData.weekProd.toString(16) + } + item { + title = "Year of Production" + value = manufacturingData.yearProd.toString(16) + } + } } } - } companion object { fun create( diff --git a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt index ba21d9da6..5563976fe 100644 --- a/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt +++ b/card/felica/src/commonMain/kotlin/com/codebutler/farebot/card/felica/FelicaCard.kt @@ -51,23 +51,31 @@ data class FelicaCard( fun getSystem(systemCode: Int): FelicaSystem? = systemsByCode[systemCode] - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = "IDm"; value = idm } - item { title = "PMm"; value = pmm } - item { - title = "Systems" - for (system in systems) { - item { - title = "System: ${system.code.toString(16)}" - for (service in system.services) { - item { - title = "Service: 0x${service.serviceCode.toString( - 16, - )} (${FelicaUtils.getFriendlyServiceName(system.code, service.serviceCode)})" - for (block in service.blocks) { - item { - title = "Block ${block.address.toString().padStart(2, '0')}" - value = block.data + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = "IDm" + value = idm + } + item { + title = "PMm" + value = pmm + } + item { + title = "Systems" + for (system in systems) { + item { + title = "System: ${system.code.toString(16)}" + for (service in system.services) { + item { + title = "Service: 0x${service.serviceCode.toString( + 16, + )} (${FelicaUtils.getFriendlyServiceName(system.code, service.serviceCode)})" + for (block in service.blocks) { + item { + title = "Block ${block.address.toString().padStart(2, '0')}" + value = block.data + } } } } @@ -75,7 +83,6 @@ data class FelicaCard( } } } - } companion object { fun create( diff --git a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt index 28e5dfcd7..adf4fe9a9 100644 --- a/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt +++ b/card/iso7816/src/commonMain/kotlin/com/codebutler/farebot/card/iso7816/ISO7816Card.kt @@ -52,44 +52,58 @@ data class ISO7816Card( fun getApplicationByName(appName: ByteArray): ISO7816Application? = applications.firstOrNull { it.appName?.toHexString() == appName.toHexString() } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { - title = "Applications" - for (app in applications) { - item { - val appNameStr = app.appName?.let { formatAID(it) } ?: "Unknown" - title = "Application: $appNameStr (${app.type})" + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = "Applications" + for (app in applications) { + item { + val appNameStr = app.appName?.let { formatAID(it) } ?: "Unknown" + title = "Application: $appNameStr (${app.type})" - // Show files - if (app.files.isNotEmpty()) { - item { - title = "Files" - for ((selector, file) in app.files) { - item { - title = "File: $selector" - if (file.binaryData != null) { - item { title = "Binary Data"; value = file.binaryData } - } - for ((index, record) in file.records.entries.sortedBy { it.key }) { - item { title = "Record $index"; value = record } + // Show files + if (app.files.isNotEmpty()) { + item { + title = "Files" + for ((selector, file) in app.files) { + item { + title = "File: $selector" + if (file.binaryData != null) { + item { + title = "Binary Data" + value = file.binaryData + } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { + title = "Record $index" + value = record + } + } } } } } - } - // Show SFI files - if (app.sfiFiles.isNotEmpty()) { - item { - title = "SFI Files" - for ((sfi, file) in app.sfiFiles.entries.sortedBy { it.key }) { - item { - title = "SFI 0x${sfi.toString(16)}" - if (file.binaryData != null) { - item { title = "Binary Data"; value = file.binaryData } - } - for ((index, record) in file.records.entries.sortedBy { it.key }) { - item { title = "Record $index"; value = record } + // Show SFI files + if (app.sfiFiles.isNotEmpty()) { + item { + title = "SFI Files" + for ((sfi, file) in app.sfiFiles.entries.sortedBy { it.key }) { + item { + title = "SFI 0x${sfi.toString(16)}" + if (file.binaryData != null) { + item { + title = "Binary Data" + value = file.binaryData + } + } + for ((index, record) in file.records.entries.sortedBy { it.key }) { + item { + title = "Record $index" + value = record + } + } } } } @@ -98,7 +112,6 @@ data class ISO7816Card( } } } - } companion object { fun create( diff --git a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt index 30a632b2d..2937ce3c4 100644 --- a/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt +++ b/card/ksx6924/src/commonMain/kotlin/com/codebutler/farebot/card/ksx6924/KSX6924PurseInfo.kt @@ -134,18 +134,49 @@ data class KSX6924PurseInfo( ListItem(Res.string.ksx6924_discount_type, resolver.resolveDisRate(disRate)), ) - suspend fun getAdvancedInfo(resolver: KSX6924PurseInfoResolver = KSX6924PurseInfoDefaultResolver): FareBotUiTree = uiTree { - item { title = Res.string.ksx6924_crypto_algorithm; value = resolver.resolveCryptoAlgo(alg) } - item { title = Res.string.ksx6924_encryption_key_version; value = vk.hexString } - item { title = Res.string.ksx6924_auth_id; value = idtr.hexString } - item { title = Res.string.ksx6924_ticket_type; value = resolver.resolveUserCode(userCode) } - item { title = Res.string.ksx6924_max_balance; value = balMax.toString() } - item { title = Res.string.ksx6924_branch_code; value = bra.hexString } - item { title = Res.string.ksx6924_one_time_limit; value = mmax.toString() } - item { title = Res.string.ksx6924_mobile_carrier; value = resolver.resolveTCode(tcode) } - item { title = Res.string.ksx6924_financial_institution; value = resolver.resolveCCode(ccode) } - item { title = Res.string.ksx6924_rfu; value = rfu.hex() } - } + suspend fun getAdvancedInfo(resolver: KSX6924PurseInfoResolver = KSX6924PurseInfoDefaultResolver): FareBotUiTree = + uiTree { + item { + title = Res.string.ksx6924_crypto_algorithm + value = resolver.resolveCryptoAlgo(alg) + } + item { + title = Res.string.ksx6924_encryption_key_version + value = vk.hexString + } + item { + title = Res.string.ksx6924_auth_id + value = idtr.hexString + } + item { + title = Res.string.ksx6924_ticket_type + value = resolver.resolveUserCode(userCode) + } + item { + title = Res.string.ksx6924_max_balance + value = balMax.toString() + } + item { + title = Res.string.ksx6924_branch_code + value = bra.hexString + } + item { + title = Res.string.ksx6924_one_time_limit + value = mmax.toString() + } + item { + title = Res.string.ksx6924_mobile_carrier + value = resolver.resolveTCode(tcode) + } + item { + title = Res.string.ksx6924_financial_institution + value = resolver.resolveCCode(ccode) + } + item { + title = Res.string.ksx6924_rfu + value = rfu.hex() + } + } override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt index acc904dc7..6dc416af2 100644 --- a/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt +++ b/card/ultralight/src/commonMain/kotlin/com/codebutler/farebot/card/ultralight/UltralightCard.kt @@ -69,17 +69,18 @@ data class UltralightCard( return result } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { - title = Res.string.ultralight_pages - for (page in pages) { - item { - title = FormattedString(Res.string.ultralight_page_title_format, page.index.toString()) - value = page.data + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = Res.string.ultralight_pages + for (page in pages) { + item { + title = FormattedString(Res.string.ultralight_page_title_format, page.index.toString()) + value = page.data + } } } } - } /** * Known Ultralight card type variants, detected via GET_VERSION command. diff --git a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt index 8dcba76c4..7f7b5fcfd 100644 --- a/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt +++ b/card/vicinity/src/commonMain/kotlin/com/codebutler/farebot/card/vicinity/VicinityCard.kt @@ -78,20 +78,24 @@ data class VicinityCard( } @OptIn(ExperimentalStdlibApi::class) - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - if (sysInfo != null) { - item { title = "System Info"; value = sysInfo } - } - item { - title = "Pages" - for (page in pages) { + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + if (sysInfo != null) { item { - title = "Page ${page.index}" - value = if (page.isUnauthorized) "Unauthorized" else page.data + title = "System Info" + value = sysInfo + } + } + item { + title = "Pages" + for (page in pages) { + item { + title = "Page ${page.index}" + value = if (page.isUnauthorized) "Unauthorized" else page.data + } } } } - } companion object { fun create( diff --git a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt index 870952b58..297d84cc2 100644 --- a/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt +++ b/transit/calypso/src/commonMain/kotlin/com/codebutler/farebot/transit/calypso/lisboaviva/LisboaVivaTransitInfo.kt @@ -21,9 +21,9 @@ package com.codebutler.farebot.transit.calypso.lisboaviva import com.codebutler.farebot.base.ui.FareBotUiTree -import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.NumberUtils import com.codebutler.farebot.base.util.byteArrayToLong @@ -62,7 +62,10 @@ class LisboaVivaTransitInfo internal constructor( override suspend fun getAdvancedUi(): FareBotUiTree? { if (tagId == null) return null return uiTree { - item { title = Res.string.calypso_engraved_serial; value = tagId.toString() } + item { + title = Res.string.calypso_engraved_serial + value = tagId.toString() + } } } diff --git a/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt b/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt index 1eaf25fdf..a432671ca 100644 --- a/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt +++ b/transit/charlie/src/commonMain/kotlin/com/codebutler/farebot/transit/charlie/CharlieCardTransitInfo.kt @@ -90,7 +90,10 @@ class CharlieCardTransitInfo internal constructor( override suspend fun getAdvancedUi(): FareBotUiTree? { if (secondSerial == 0L || secondSerial == 0xffffffffL) return null return uiTree { - item { title = Res.string.charlie_2nd_card_number; value = "A" + NumberUtils.zeroPad(secondSerial, 10) } + item { + title = Res.string.charlie_2nd_card_number + value = "A" + NumberUtils.zeroPad(secondSerial, 10) + } } } diff --git a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt index 5cdcca59e..7c53119e7 100644 --- a/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt +++ b/transit/clipper/src/commonMain/kotlin/com/codebutler/farebot/transit/clipper/ClipperTrip.kt @@ -126,5 +126,4 @@ class ClipperTrip( vehicleNum, transportCode, ) - } diff --git a/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt b/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt index 8fe907736..ddee3a615 100644 --- a/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt +++ b/transit/hsl/src/commonMain/kotlin/com/codebutler/farebot/transit/hsl/HSLTransitInfo.kt @@ -52,12 +52,33 @@ class HSLTransitInfo( get() = TransitBalance(balance = TransitCurrency.EUR(mBalance)) override suspend fun getAdvancedUi(): FareBotUiTree? { - val tree = uiTree { - applicationVersion?.let { item { title = Res.string.hsl_application_version; value = it } } - applicationKeyVersion?.let { item { title = Res.string.hsl_application_key_version; value = it } } - platformType?.let { item { title = Res.string.hsl_platform_type; value = it } } - securityLevel?.let { item { title = Res.string.hsl_security_level; value = it } } - } + val tree = + uiTree { + applicationVersion?.let { + item { + title = Res.string.hsl_application_version + value = it + } + } + applicationKeyVersion?.let { + item { + title = Res.string.hsl_application_key_version + value = it + } + } + platformType?.let { + item { + title = Res.string.hsl_platform_type + value = it + } + } + securityLevel?.let { + item { + title = Res.string.hsl_security_level + value = it + } + } + } return if (tree.items.isEmpty()) null else tree } } diff --git a/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt b/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt index 3f52201dc..a8a65eac5 100644 --- a/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt +++ b/transit/nextfareul/src/commonMain/kotlin/com/codebutler/farebot/transit/nextfareul/NextfareUltralightTransitData.kt @@ -23,9 +23,9 @@ package com.codebutler.farebot.transit.nextfareul import com.codebutler.farebot.base.ui.FareBotUiTree -import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.Luhn import com.codebutler.farebot.base.util.NumberUtils @@ -103,9 +103,13 @@ abstract class NextfareUltralightTransitData : TransitInfo() { return items } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = Res.string.nextfareul_machine_code; value = capsule.mMachineCode.toString(16) } - } + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = Res.string.nextfareul_machine_code + value = capsule.mMachineCode.toString(16) + } + } protected abstract fun makeCurrency(value: Int): TransitCurrency diff --git a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt index d6b08cc2c..351a54fd7 100644 --- a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt +++ b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipIndex.kt @@ -38,13 +38,23 @@ data class OVChipIndex internal constructor( val recentCreditSlot: Boolean, // Most recent credit index slot (0xF90(false) or 0xFA0(true)) val subscriptionIndex: List, ) { - fun advancedItems(): List = listOf( - FareBotUiTree.Item(title = FormattedString("Transaction Slot"), value = if (recentTransactionSlot) "B" else "A"), - FareBotUiTree.Item(title = FormattedString("Info Slot"), value = if (recentInfoSlot) "B" else "A"), - FareBotUiTree.Item(title = FormattedString("Subscription Slot"), value = if (recentSubscriptionSlot) "B" else "A"), - FareBotUiTree.Item(title = FormattedString("Travelhistory Slot"), value = if (recentTravelhistorySlot) "B" else "A"), - FareBotUiTree.Item(title = FormattedString("Credit Slot"), value = if (recentCreditSlot) "B" else "A"), - ) + fun advancedItems(): List = + listOf( + FareBotUiTree.Item( + title = FormattedString("Transaction Slot"), + value = if (recentTransactionSlot) "B" else "A", + ), + FareBotUiTree.Item(title = FormattedString("Info Slot"), value = if (recentInfoSlot) "B" else "A"), + FareBotUiTree.Item( + title = FormattedString("Subscription Slot"), + value = if (recentSubscriptionSlot) "B" else "A", + ), + FareBotUiTree.Item( + title = FormattedString("Travelhistory Slot"), + value = if (recentTravelhistorySlot) "B" else "A", + ), + FareBotUiTree.Item(title = FormattedString("Credit Slot"), value = if (recentCreditSlot) "B" else "A"), + ) companion object { fun parse(data: ByteArray): OVChipIndex { diff --git a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt index 02c5494c6..f6d759c90 100644 --- a/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt +++ b/transit/ovc/src/commonMain/kotlin/com/codebutler/farebot/transit/ovc/OVChipTransitInfo.kt @@ -25,10 +25,10 @@ package com.codebutler.farebot.transit.ovc import com.codebutler.farebot.base.ui.FareBotUiTree -import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.HeaderListItem import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.transit.Subscription import com.codebutler.farebot.transit.TransitBalance @@ -149,14 +149,21 @@ class OVChipTransitInfo( return li } - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = "Credit Slot ID"; value = creditSlotId.toString() } - item { title = "Last Credit ID"; value = creditId.toString() } - item { - title = "Recent Slots" - addChildren(index.advancedItems()) + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = "Credit Slot ID" + value = creditSlotId.toString() + } + item { + title = "Last Credit ID" + value = creditId.toString() + } + item { + title = "Recent Slots" + addChildren(index.advancedItems()) + } } - } companion object { private val NAME = FormattedString("OV-chipkaart") diff --git a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt index 3a841ccd8..0f2a1d156 100644 --- a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt +++ b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTransitFactory.kt @@ -32,12 +32,12 @@ import com.codebutler.farebot.transit.TransitFactory import com.codebutler.farebot.transit.TransitIdentity import com.codebutler.farebot.transit.TransitRegion import com.codebutler.farebot.transit.Trip -import kotlin.time.Instant import com.codebutler.farebot.transit.seqgo.record.SeqGoBalanceRecord import com.codebutler.farebot.transit.seqgo.record.SeqGoRecord import com.codebutler.farebot.transit.seqgo.record.SeqGoTapRecord import com.codebutler.farebot.transit.seqgo.record.SeqGoTopupRecord import farebot.transit.seqgo.generated.resources.* +import kotlin.time.Instant class SeqGoTransitFactory : TransitFactory { override val allCards: List @@ -138,7 +138,7 @@ class SeqGoTransitFactory : TransitFactory { endStationId = endStationId, startStationValue = SeqGoUtil.getStation(tapOn.station), endStationValue = endStation, - ) + ), ) i++ } diff --git a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt index 1c6fa66e7..49cba4dd2 100644 --- a/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt +++ b/transit/seqgo/src/commonMain/kotlin/com/codebutler/farebot/transit/seqgo/SeqGoTrip.kt @@ -74,5 +74,4 @@ class SeqGoTrip( fun getStartStationId(): Int = startStationId fun getEndStationId(): Int = endStationId - } diff --git a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt index 35e05b1e1..f89dff611 100644 --- a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt +++ b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/HoloTransitInfo.kt @@ -11,9 +11,9 @@ package com.codebutler.farebot.transit.serialonly import com.codebutler.farebot.base.ui.FareBotUiTree -import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import farebot.transit.serialonly.generated.resources.Res import farebot.transit.serialonly.generated.resources.last_transaction @@ -44,9 +44,13 @@ class HoloTransitInfo( ), ) - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = Res.string.manufacture_id; value = mManufacturingId } - } + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = Res.string.manufacture_id + value = mManufacturingId + } + } override val reason get() = Reason.NOT_STORED override val cardName get() = HoloTransitFactory.NAME diff --git a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt index cf3aca3fe..61e4fdf87 100644 --- a/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt +++ b/transit/serialonly/src/commonMain/kotlin/com/codebutler/farebot/transit/serialonly/StrelkaTransitInfo.kt @@ -20,9 +20,13 @@ import farebot.transit.serialonly.generated.resources.strelka_long_serial class StrelkaTransitInfo( private val mSerial: String, ) : SerialOnlyTransitInfo() { - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = Res.string.strelka_long_serial; value = mSerial } - } + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = Res.string.strelka_long_serial + value = mSerial + } + } override val reason get() = Reason.MORE_RESEARCH_NEEDED override val serialNumber get() = StrelkaTransitFactory.formatShortSerial(mSerial) diff --git a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt index 47054ac0c..c71c7ef67 100644 --- a/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt +++ b/transit/smartrider/src/commonMain/kotlin/com/codebutler/farebot/transit/smartrider/SmartRiderTransitInfo.kt @@ -102,11 +102,23 @@ class SmartRiderTransitInfo( override val subscriptions: List? = null - override suspend fun getAdvancedUi(): FareBotUiTree = uiTree { - item { title = Res.string.smartrider_ticket_type; value = mTokenType.toString() } - if (mSmartRiderType == SmartRiderType.SMARTRIDER) { - item { title = Res.string.smartrider_autoload_threshold; value = TransitCurrency.AUD(mAutoloadThreshold).formatCurrencyString(true) } - item { title = Res.string.smartrider_autoload_value; value = TransitCurrency.AUD(mAutoloadValue).formatCurrencyString(true) } + override suspend fun getAdvancedUi(): FareBotUiTree = + uiTree { + item { + title = Res.string.smartrider_ticket_type + value = mTokenType.toString() + } + if (mSmartRiderType == SmartRiderType.SMARTRIDER) { + item { + title = Res.string.smartrider_autoload_threshold + value = + TransitCurrency.AUD(mAutoloadThreshold).formatCurrencyString(true) + } + item { + title = Res.string.smartrider_autoload_value + value = + TransitCurrency.AUD(mAutoloadValue).formatCurrencyString(true) + } + } } - } } diff --git a/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt b/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt index fb06c9954..ba5dc0e4d 100644 --- a/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt +++ b/transit/src/commonMain/kotlin/com/codebutler/farebot/transit/Station.kt @@ -103,6 +103,5 @@ data class Station( latitude = latitude?.toFloatOrNull(), longitude = longitude?.toFloatOrNull(), ) - } } diff --git a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt index 9a07443bb..03b1de5a9 100644 --- a/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt +++ b/transit/troika/src/commonMain/kotlin/com/codebutler/farebot/transit/troika/TroikaHybridTransitInfo.kt @@ -106,14 +106,18 @@ class TroikaHybridTransitInfo( get() = troika.warning override suspend fun getAdvancedUi(): FareBotUiTree? { - val trees = listOfNotNull( - troika.getAdvancedUi(), - podorozhnik?.getAdvancedUi(), - strelka?.getAdvancedUi(), - ) + val trees = + listOfNotNull( + troika.getAdvancedUi(), + podorozhnik?.getAdvancedUi(), + strelka?.getAdvancedUi(), + ) if (trees.isEmpty()) return null - return FareBotUiTree(items = trees.flatMap { tree -> - tree.items.map { FareBotUiTree.Item(title = it.title, value = it.value) } - }) + return FareBotUiTree( + items = + trees.flatMap { tree -> + tree.items.map { FareBotUiTree.Item(title = it.title, value = it.value) } + }, + ) } } diff --git a/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt b/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt index 12cd33e2b..dc1b3bbca 100644 --- a/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt +++ b/transit/umarsh/src/commonMain/kotlin/com/codebutler/farebot/transit/umarsh/UmarshTransitInfo.kt @@ -23,9 +23,9 @@ package com.codebutler.farebot.transit.umarsh import com.codebutler.farebot.base.ui.FareBotUiTree -import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.ui.ListItem import com.codebutler.farebot.base.ui.ListItemInterface +import com.codebutler.farebot.base.ui.uiTree import com.codebutler.farebot.base.util.FormattedString import com.codebutler.farebot.base.util.NumberUtils import com.codebutler.farebot.transit.Subscription @@ -85,7 +85,10 @@ class UmarshTransitInfo( if (rubSectors.isEmpty()) return null return uiTree { for (sec in rubSectors) { - item { title = Res.string.umarsh_machine_id; value = sec.machineId.toString() } + item { + title = Res.string.umarsh_machine_id + value = sec.machineId.toString() + } } } }