Integrate preset loading into PeriodSelectionBloc
epic-bufdir-report-period-selection-ui-integration-task-002 — Implement the LoadPresets event handler inside PeriodSelectionBloc to call PeriodPresetService, map results to PeriodPreset models, and emit a PeriodSelectionLoaded state with the preset list. Handle loading and error states. Ensure default preset is auto-selected on first load.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Handles integration between different epics or system components. Requires coordination across multiple development streams.
Implementation Notes
Use try/catch around the await — do not use catchError on futures inside blocs as it makes control flow harder to read. For the default selection logic, use firstWhereOrNull from collection package: `presets.firstWhereOrNull((p) => p.isDefault) ?? presets.first`. Guard against empty preset list from service with a dedicated error state message rather than a runtime exception.
After emitting PeriodSelectionLoaded, immediately dispatch a FetchRecordCount event internally using add(FetchRecordCount()) so the record count begins loading for the auto-selected preset — this gives a smooth UX on screen open.
Testing Requirements
Unit tests with bloc_test. Required test cases: (1) success path — mock service returns 3 presets, one marked isDefault, verify emitted loaded state has correct selectedPreset; (2) no-default fallback — mock returns list with no isDefault, verify first item is selected; (3) error path — mock service throws Exception, verify PeriodSelectionError emitted; (4) refresh path — bloc already in loaded state with a selected preset, dispatch LoadPresets again with updated list containing same preset ID, verify selectedPreset is preserved; (5) refresh removes selected preset — preset ID no longer in new list, verify first preset selected instead. Use MockPeriodPresetService via mockito or manual stub. 100% branch coverage on the LoadPresets handler.
The record count query is asynchronous and may take up to 1 second on a slow connection. If the BLoC does not manage loading states carefully, the UI may show a stale count or the confirm button may be briefly enabled during the loading transition, allowing premature submission.
Mitigation & Contingency
Mitigation: Define explicit BLoC states: PeriodSelectionLoading, PeriodSelectionCountLoaded, PeriodSelectionValidationError, and PeriodSelectionReady. The confirm button must only be enabled in PeriodSelectionReady. Add a debounce of 300ms on period-change events before triggering the count query to prevent excessive calls.
Contingency: If debouncing is insufficient to prevent UX degradation on slow connections, add an optimistic loading skeleton to the RecordCountBanner that clearly communicates a pending state, and keep the confirm button disabled until the count resolves.
The confirmedReportPeriodProvider Riverpod contract between this feature and the Bufdir Export feature may not be defined yet, causing integration failures when the downstream export screen attempts to read the provider.
Mitigation & Contingency
Mitigation: Define the confirmedReportPeriodProvider as a StateProvider<DateTimeRange?> in a shared providers file at the start of this epic, before any screen code is written. Communicate the provider contract to the Bufdir Export feature team so both sides align on the same provider reference.
Contingency: If the export feature consumes the period through a different mechanism (e.g., navigation arguments), add a thin adapter that writes the confirmed period to both the Riverpod provider and the navigation argument, ensuring backward compatibility.
Flutter's live region support for screen readers is inconsistent across platforms (iOS VoiceOver vs Android TalkBack), and the record count banner must be announced on every change — a pattern that has historically required platform-channel workarounds.
Mitigation & Contingency
Mitigation: Test the live region announcement on both iOS (VoiceOver) and Android (TalkBack) early in implementation using the accessibility_test harness. Reference the project's existing live-region-announcer component (664-accessibility-live-region-announcer) for a proven implementation pattern.
Contingency: If native live region support is insufficient, wrap the record count text in a Semantics widget with a programmatically updated label string, and trigger a SemanticsService.announce call on each count update as a platform-agnostic fallback.