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
| Prop | Type | Required | Description |
|---|---|---|---|
isLoading | bool | Yes | When true, shows the overlay on top of child |
child | Widget | Yes | Underlying screen content |
message | String? | No | Optional 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
| Prop | Type | Description |
|---|---|---|
child | Widget | Screen 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.
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
| Prop | Type | Description |
|---|---|---|
currentStage | int | Stage 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
],
),
),
),
),
);