Implement PauseReactivateToggle profile control
epic-peer-mentor-pause-ui-state-task-005 — Build the PauseReactivateToggle widget for the peer mentor profile screen. It renders an accessible status badge (Active / Paused) and a single action button whose label and behaviour switch contextually. On pause intent it opens PauseConfirmationDialog; on reactivate intent it dispatches directly. Use design token colours and meet 44×44 dp touch-target minimum.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Keep PauseReactivateToggle as a pure presentational widget in this task — pass MentorStatus as a constructor parameter and callbacks for onPauseConfirmed/onReactivate. BLoC wiring belongs in task-009. Use a ternary on MentorStatus to switch badge colour token and button label; avoid duplicating layout logic. For the 44 dp touch target, wrap AppButton with a SizedBox(width: 44, height: 44) minimum and set minimumSize in ButtonStyle if AppButton accepts styleOverride.
Design tokens should be accessed via Theme.of(context).extension
Testing Requirements
Widget tests: render with MentorStatus.active — assert badge text 'Active', button label 'Pause'. Widget tests: render with MentorStatus.paused — assert badge text 'Paused', button label 'Reactivate'. Widget tests: tap pause button, assert PauseConfirmationDialog is pushed. Widget tests: tap reactivate button, assert ReactivateMentorEvent is dispatched (mock BLoC).
Widget tests: verify touch target >= 44dp using tester.getSize(). Golden tests: snapshot both active and paused states for visual regression.
PauseConfirmationDialog must meet WCAG 2.2 AA focus trap requirements: when the dialog opens, focus must move to the first interactive element; when it closes, focus must return to the triggering toggle. Flutter's default showDialog does not always guarantee correct focus restoration on Android, which could trap screen-reader users.
Mitigation & Contingency
Mitigation: Use a custom modal implementation wrapping Flutter's Dialog with explicit FocusScope management and test with TalkBack on Android and VoiceOver on iOS during development, not just in final QA. Reference the accessible-modal-sheet pattern already used elsewhere in the codebase.
Contingency: If full WCAG focus management cannot be achieved within the sprint, ship the feature with a documented accessibility defect and schedule a dedicated accessibility remediation task. Communicate the known gap to the accessibility stakeholder at HLF/NHF.
If the user taps the PauseReactivateToggle rapidly before the BLoC emits a loading state, multiple PauseMentorEvents could be added to the BLoC stream, resulting in duplicate service calls and inconsistent UI state (toggle flickering between states).
Mitigation & Contingency
Mitigation: Disable the toggle widget immediately on first tap by emitting MentorStatusLoading synchronously before the async service call begins. Use BLoC's event transformer with droppable() to discard subsequent events while a transition is in progress.
Contingency: If BLoC event deduplication is not sufficient, add a debounce at the widget level (300 ms) and a server-side idempotency check in MentorStatusService that no-ops if the requested transition is already in progress.
PauseStatusBanner must conditionally render the reactivate shortcut only for users with coordinator permission. If the permission check relies on a stale role state in the Riverpod provider, a peer mentor could briefly see a reactivate action they are not authorised to use, which could cause a confusing permission-denied error if tapped.
Mitigation & Contingency
Mitigation: Source the permission check directly from the role-resolution Riverpod provider (which is kept in sync with Supabase auth) rather than from local component state. Add a widget test asserting the shortcut is absent when the user role is peer_mentor.
Contingency: If the shortcut is accidentally shown to an unauthorised user, the underlying MentorStatusService enforces role validation server-side, so the worst outcome is a visible error message rather than an actual unauthorised state change.