For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Simplify the three-step consumption UI while preserving every business stage, and display the card holder plus subsidy and cash balances after a successful card read.
Architecture: Keep ConsumptionFlowStateMachine as the business source of truth. Add a pure-Java presentation mapper that groups business states into user-facing states, extend the existing transaction progress callback with the first complete CardInfo snapshot, and let ConsumptionActivity render one stable third-step panel. Resolve the holder name from the local customer database by customerId; use the real after-read snapshot for final balances.
Tech Stack: Android Java, XML resources, SQLite customer repository, JUnit 4, Gradle.
app/src/main/java/com/zhct/traybinding/consumption/ConsumptionUiStateMapper.java: map business states and card presence to user-facing states.app/src/test/java/com/zhct/traybinding/consumption/ConsumptionUiStateMapperTest.java: lock the presentation grouping.app/src/main/java/com/zhct/traybinding/onecard/reader/ConsumptionCardTransactionExecutor.java: publish the first complete card snapshot.app/src/main/java/com/zhct/traybinding/onecard/reader/ConsumptionCardSearchManager.java: forward the snapshot to the Activity on the main thread.app/src/test/java/com/zhct/traybinding/onecard/reader/ConsumptionCardTransactionExecutorTest.java: verify snapshot timing and zero/positive paths.app/src/main/java/com/zhct/traybinding/main/ConsumptionActivity.java: remove the order-success holding page, resolve holder names, retain the latest verified balances, and render grouped UI states.app/src/main/res/layout/activity_consumption.xml: add the compact holder/balance area and restore the illustration view using new assets.app/src/main/res/values/strings.xml, dimens.xml, colors.xml, styles.xml: resource all new copy and dimensions.app/src/main/res/drawable-nodpi/consumption_illustration_*.png assets: tray placement, card placement, card removal.Create parameterized assertions covering:
assertEquals(UiState.CONFIRMING_ORDER, map(State.LOADING_ORDER, false));
assertEquals(UiState.PLACE_CARD, map(State.WAITING_CARD, false));
assertEquals(UiState.REMOVE_CARD, map(State.WAITING_CARD, true));
assertEquals(UiState.PROCESSING_CARD, map(State.READING_BEFORE, true));
assertEquals(UiState.PROCESSING_CARD, map(State.DEBITING, true));
assertEquals(UiState.PROCESSING_CARD, map(State.READING_AFTER, true));
assertEquals(UiState.COMPLETED, map(State.REPORTING, true));
assertEquals(UiState.COMPLETED, map(State.REPORT_PENDING, true));
Use this public boundary:
public final class ConsumptionUiStateMapper {
public static UiState resolve(
ConsumptionFlowStateMachine.State state, boolean cardPresent) {
if (state == null) {
throw new IllegalArgumentException("Business state is required");
}
switch (state) {
case WAITING_TRAY:
return UiState.WAITING_TRAY;
case LOADING_ORDER:
return UiState.CONFIRMING_ORDER;
case ORDER_FAILED:
return UiState.ORDER_FAILED;
case WAITING_CARD:
return cardPresent ? UiState.REMOVE_CARD : UiState.PLACE_CARD;
case READING_ZERO_AMOUNT_CARD:
case READING_BEFORE:
case DEBITING:
case READING_AFTER:
return UiState.PROCESSING_CARD;
case REPORTING:
case COMPLETED:
case REPORT_PENDING:
return UiState.COMPLETED;
case TRANSACTION_FAILED:
return UiState.TRANSACTION_FAILED;
case TRANSACTION_UNKNOWN:
return UiState.TRANSACTION_UNKNOWN;
default:
throw new IllegalStateException("Unsupported business state: " + state);
}
}
public enum UiState {
WAITING_TRAY, CONFIRMING_ORDER, ORDER_FAILED,
PLACE_CARD, REMOVE_CARD, PROCESSING_CARD,
COMPLETED, TRANSACTION_FAILED, TRANSACTION_UNKNOWN
}
}
Run: JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home bash ./gradlew :app:testDebugUnitTest --tests '*ConsumptionUiStateMapperTest' --no-daemon --console=plain
Expected: the mapper test passes.
Keep onStage(Stage) as the only abstract method and add:
default void onCardRead(CardInfo cardInfo) {
}
Call onCardRead(beforeCard) immediately after readCard(expectedUid) succeeds and before any zero-amount return or debit command.
Add this manager listener method:
void onCardInfoRead(String orderNo, CardInfo cardInfo);
The manager posts it through mainHandler only for the active session and current transaction order.
Add a test listener that records BEFORE_READ, CARD_READ, DEBIT, and AFTER_READ. Assert CARD_READ occurs after the successful first read and before DEBIT; zero amount emits one card snapshot and never debits.
Use transparent-background, text-free blue/cyan artwork with matching canvas sizes. Save as:
consumption_illustration_place_tray.png
consumption_illustration_place_card.png
consumption_illustration_remove_card.png
Add a hidden consumption_card_info container with these value views:
consumption_card_holder_name
consumption_subsidy_balance
consumption_cash_balance
consumption_balance_context
Use resource dimensions and strings; do not add literal user-facing text or magic sizes to the layout.
The order panel only contains the confirming/failure title and message. The payment panel contains one illustration, one amount, one action title/message, and the card information region. Remove the visible order number from the user panel.
Delete ORDER_CONFIRMATION_DISPLAY_MS, orderConfirmationVisible, and the delayed order-success page. After flow.onOrderLoaded(amount), call armCurrentOrder() directly.
Create SQLiteCustomerRepository in onCreate, resolve NAME through a named field constant, and store the current CardInfo, holder name, and balance context. Missing names display the resource string 持卡人信息未同步 and never block payment.
When an afterCard snapshot exists, show it as the latest read balance. Only after the complete positive transaction passes validation mark it as 结算后余额. Zero amount remains 卡片余额; uncertain outcomes must not use the final-balance label.
Drive renderPayment() from ConsumptionUiStateMapper.UiState. Map all protected card stages to one processing title/message and image; map reporting, completed, and report-pending to one completion title/message and removal image.
Clear card snapshot, holder name, and balance context both when a new tray session starts and when the terminal reset runnable releases the prior session. Close the customer repository in onDestroy().
Update the product requirements to V0.9.6, code design to V1.2, Scheme B UI specification to V1.1, implementation worklog with a new record, and add an independent change record. Regenerate matching HTML from each Markdown source.
Run the presentation mapper and transaction executor tests. Expected: both test classes pass.
Run: JAVA_HOME=/Users/liang/Library/Java/JavaVirtualMachines/corretto-1.8.0_482/Contents/Home bash ./gradlew :app:assembleDebug --no-daemon --console=plain
Expected: BUILD SUCCESSFUL. Do not perform device automation, screenshot comparison, full regression, Git staging, commit, push, branch changes, or history rewriting.
Confirm the pre-change archive SHA-256 remains 1bcc13616f874ffde492ef57608c5dadd16f72c52073416d42a2cee145b90cfa, and list every new source/resource/test file in the change record so a rollback can restore existing files and remove only files introduced by this change.