high priority medium complexity frontend pending frontend specialist Tier 6

Acceptance Criteria

A share button is visible on each slide and tapping it opens the SummaryShareOverlay as a modal bottom sheet or full-screen overlay
The overlay presents share options (e.g. copy image, share via system sheet) and a confirm action
Confirming in the overlay dispatches ShareSlide(slideIndex: currentSlideIndex) to the BLoC and closes the overlay immediately
While the BLoC is in WrappedSummarySharing state, a loading indicator is shown on the screen (not blocking navigation) and the share button is disabled
On WrappedSummaryShareError, the overlay is not re-shown; instead a SnackBar appears with a human-readable error message and a retry action that re-dispatches ShareSlide
On successful share (BLoC returns to WrappedSummaryLoaded), the overlay is fully dismissed with no residual UI artifacts
The overlay can be cancelled by tapping outside or pressing the device back button without dispatching any BLoC event
Share button has a minimum touch target of 48x48dp and a semantic label ('Share this slide') for screen reader users
SnackBar error message is localizable — no hardcoded strings
The loading indicator does not block swipe navigation between slides while sharing is in progress

Technical Requirements

frameworks
Flutter
BLoC
flutter_bloc
apis
platform share_plus plugin or Flutter's native Share.share()
data models
ShareSlide
WrappedSummarySharing
WrappedSummaryShareError
WrappedSummaryLoaded
performance requirements
Overlay must open in under one frame — use showModalBottomSheet with no heavy computation on the open call
Share operation timeout should be handled at the BLoC/service level; UI must not hang indefinitely
security requirements
Shared image/content must not include any personally identifiable information beyond what the peer mentor explicitly chooses to share
Confirm with design/product that generated share images do not include the peer mentor's full name without consent
ui components
SummaryShareOverlay
showModalBottomSheet or showDialog
SnackBar
share IconButton per slide
CircularProgressIndicator overlay

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use BlocListener (not BlocBuilder) to react to WrappedSummaryLoaded-after-sharing and WrappedSummaryShareError state transitions — this is a side-effect pattern, not a UI rebuild pattern. The listener should: on success → call Navigator.pop() if the overlay is still open; on error → show ScaffoldMessenger.of(context).showSnackBar(). Track whether the overlay is open using a local bool _isShareOverlayOpen to avoid calling Navigator.pop() redundantly. For the share button on each slide, pass a VoidCallback onShare to each slide widget rather than giving slides direct BLoC access — keeps slide widgets testable in isolation.

The SummaryShareOverlay itself should not know about the BLoC; it receives an onConfirm callback and an onCancel callback. This separation means the overlay can be tested and reused independently. Ensure the loading indicator while sharing is a subtle overlay (e.g. a small spinner in the app bar area) rather than a full-screen blocker, so the peer mentor can still read their slide data while the share completes.

Testing Requirements

Widget tests: (1) find share button on slide 0 and tap it, verify SummaryShareOverlay is shown, (2) tap confirm in overlay, verify ShareSlide(slideIndex: 0) was dispatched and overlay is dismissed, (3) emit WrappedSummarySharing and verify share button is disabled and loading indicator is visible, (4) emit WrappedSummaryShareError and verify SnackBar is shown with non-empty error text, (5) tap cancel in overlay without confirming and verify no BLoC event was dispatched. Use BlocListener in the test to capture dispatched events. Integration test (optional): use flutter_driver or integration_test to verify end-to-end share flow on a real device.

Component
Wrapped Summary Screen
ui high
Epic Risks (3)
medium impact medium prob technical

If the device transitions between online and offline states while the user is mid-session in the wrapped screen, the BLoC may emit conflicting state transitions (loaded → error → offline) that cause visual flickering or an inconsistent UI state such as showing the offline banner over an already-loaded summary.

Mitigation & Contingency

Mitigation: Implement a connectivity stream listener in the BLoC that only triggers a state re-evaluation when transitioning from online to offline, not on every connectivity event. Once a summary is in the Loaded state, the BLoC should not transition to error/offline unless the user explicitly requests a refresh. Store the last-loaded data in BLoC state so it survives connectivity changes.

Contingency: If state flickering is observed in testing, add a minimum 3-second debounce on connectivity state changes before the BLoC reacts, and display a non-blocking top banner rather than replacing the entire screen state.

high impact medium prob integration

The push notification deep-link to the wrapped-summary-screen must work correctly whether the app is in the foreground, background, or terminated state. Handling all three app launch states on both iOS and Android is a common source of edge-case bugs, particularly when authentication state must be restored before the deep link can be resolved.

Mitigation & Contingency

Mitigation: Implement deep-link handling through the existing notification-deep-link-handler component which already manages app-state-aware routing. Define the wrapped-summary route in the navigation config early in the epic so the router is ready before notification dispatch is wired. Test all three app states (foreground, background, terminated) explicitly in the QA checklist.

Contingency: If terminated-state deep-linking fails on specific platforms, fall back to launching the app to the home screen with an in-app notification banner prompting the user to open their summary, rather than direct deep-link navigation.

high impact low prob technical

The wrapped-summary-screen manages a large number of AnimationController instances (one or more per slide) via the wrapped-animation-controller. If disposal is not triggered correctly when the user exits mid-flow (e.g., via system back gesture or deep-link away), memory leaks will accumulate across session navigation.

Mitigation & Contingency

Mitigation: Implement screen disposal via Flutter's dispose() lifecycle method calling a single wrapped-animation-controller.disposeAll() method that iterates the named controller registry. Write a test that navigates to the screen, starts animations, then navigates away and verifies no active AnimationController listeners remain using Flutter's test binding.

Contingency: If disposal bugs are detected in production via memory profiling, patch by converting all AnimationControllers to use AutomaticKeepAliveClientMixin false and wrap each slide in a widget that disposes its own controller when removed from the widget tree.