08 - Widgets Reference

Five wrapper widgets handle cross-cutting concerns across stage screens. They compose via nesting - no mixins, no base classes, no inheritance.

1. LoadingOverlay

Full-screen loading spinner with optional message. Uses Lottie animation (ic_loader.json). Conditionally rendered on top of screen content.

Props

PropTypeRequiredDescription
isLoadingboolYesWhen true, shows the overlay on top of child
childWidgetYesUnderlying screen content
messageString?NoOptional message below the loader

Usage

DartTypical usage in a stage screen
LoadingOverlay(
  isLoading: _isLoading,
  message: 'Verifying bank account...',
  child: Column(
    children: [
      _buildForm(),
      _buildProceedButton(),
    ],
  ),
)

Source

flutter_app/lib/widgets/loading_overlay.dartDart
class LoadingOverlay extends StatelessWidget {
  final bool isLoading;
  final Widget child;
  final String? message;

  const LoadingOverlay({
    super.key,
    required this.isLoading,
    required this.child,
    this.message,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        child,
        if (isLoading)
          Container(
            color: (message != null && message!.isNotEmpty)
                ? Colors.white
                : Colors.black.withOpacity(0.5),
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Lottie.asset(
                    AppAssets.loader,
                    height: 80, width: 100,
                  ),
                  if (message != null && message!.isNotEmpty) ...[
                    const SizedBox(height: 25),
                    Text(message!, ...),
                  ],
                ],
              ),
            ),
          ),
      ],
    );
  }
}

2. SessionTimer

Watches for user inactivity. After 13 minutes shows a warning dialog ("Your session will expire in 2 minutes"). After 15 minutes clears the journey and returns to Stage 0.

Props

PropTypeDescription
childWidgetScreen content to monitor

Usage

DartWrap the Scaffold
SessionTimer(
  child: Scaffold(
    appBar: AppBar(title: const Text('Bank')),
    body: ...,
  ),
)

SessionTimer reads ApiConfig.sessionTimeout (15 min) and sessionWarningAt (13 min) constants. It uses a periodic Timer.periodic to check StorageService.lastActivity.

When not to use

Stage 0 (arrival) does not need SessionTimer - there's nothing to time out yet. Stage 15 (congrats) also skips it since the journey is complete.

3. BackButtonHandler

Intercepts the device/browser back button. Shows a bottom sheet: "KYC is a one-time process. If you exit now, you may need to start over." Gives the user Exit or Continue options.

Usage

DartOutermost wrapper
return BackButtonHandler(
  child: SessionTimer(
    child: Scaffold(...),
  ),
);

Uses Flutter's PopScope (or WillPopScope for older Flutter versions) to intercept back navigation.

4. JourneyProgressBar

The striped horizontal progress bar shown at the top of every stage screen (after Stage 0). Displays current stage name on the left, percent completed on the right, striped fill that grows with progress.

Props

PropTypeDescription
currentStageintStage index (0-15). Determines displayed name and fill percent.

Usage

DartUsage
const JourneyProgressBar(currentStage: 6),

Striped fill

The striped diagonal effect is rendered by a custom StripedProgressPainter (a CustomPainter). It draws diagonal bars of alternating brightness in the AppTheme.progress color.

flutter_app/lib/widgets/striped_progress_painter.dartDart
class StripedProgressPainter extends CustomPainter {
  final double progress;
  StripedProgressPainter(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    // Fill background
    final bgPaint = Paint()..color = AppTheme.progressBg;
    canvas.drawRect(Offset.zero & size, bgPaint);

    // Fill progress width
    final fillWidth = size.width * progress;

    // Draw diagonal stripes
    // ...
  }
}

5. ErrorDialog

Static helper methods for error and success feedback. Not a widget class you instantiate - just call the static methods.

API

flutter_app/lib/widgets/error_dialog.dartDart
class ErrorDialog {
  // Show a blocking error dialog
  static Future<void> show(
    BuildContext context, {
    required String message,
    String? title,
  }) async { ... }

  // Show a non-blocking snackbar
  static void showSnackBar(
    BuildContext context, {
    required String message,
  }) { ... }

  // Show a success snackbar (green)
  static void showSuccess(
    BuildContext context, {
    required String message,
  }) { ... }

  // Show a retry dialog
  static Future<bool> showRetry(
    BuildContext context, {
    required String message,
  }) async { ... }
}

Usage

DartExamples
// Blocking error
ErrorDialog.show(context, message: 'Invalid OTP');

// Non-blocking error snackbar
ErrorDialog.showSnackBar(context, message: 'Upload failed');

// Success snackbar
ErrorDialog.showSuccess(context, message: 'Bank verified!');

// Retry dialog
final shouldRetry = await ErrorDialog.showRetry(
  context,
  message: 'Network error. Retry?',
);
if (shouldRetry) { ... }

Other Reusable Widgets

OtpInput

4-box OTP input field. Auto-focus next box on digit entry. Auto-submit on last digit. Used in Stage 2 and Stage 3.

flutter_app/lib/widgets/otp_input.dartDart
OtpInput(
  length: 4,
  onCompleted: (otp) => _verify(otp),
  onChanged: (otp) => setState(() {}),
)

ConsentCheckbox

Checkbox with rich text label. Used for T&C consent in Stage 1.

flutter_app/lib/widgets/consent_checkbox.dartDart
ConsentCheckbox(
  value: _acceptedTerms,
  onChanged: (v) => setState(() => _acceptedTerms = v),
  label: 'I accept the Terms & Conditions',
  linkText: 'Terms & Conditions',
  linkUrl: 'https://motilaloswal.com/tnc',
)

Composition Pattern

See how the wrappers nest:

DartFull wrapper stack in a stage screen
return BackButtonHandler(              // 1. Intercept back button
  child: SessionTimer(                  // 2. Monitor inactivity
    child: Scaffold(                     // 3. Material Scaffold
      appBar: AppBar(title: const Text('Bank Verification')),
      body: LoadingOverlay(              // 4. Full-screen loader
        isLoading: _isLoading,
        child: Column(
          children: [
            const JourneyProgressBar(     // 5. Striped progress
              currentStage: 6,
            ),
            _buildBody(),              // 6. Screen-specific content
          ],
        ),
      ),
    ),
  ),
);