Consumption UI Simplification Implementation Plan

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.


File structure

Task 1: Add the presentation mapping

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.

Task 2: Publish the first complete card snapshot

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.

Task 3: Build the simplified third-step layout

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.

Task 4: Integrate holder and balance rendering

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().

Task 5: Record and perform lightweight verification

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.