166 lines
4.5 KiB
Dart
166 lines
4.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../theme/m3_motion.dart';
|
|
|
|
/// A centered error state with an icon, title, human-readable message and an
|
|
/// optional retry button.
|
|
///
|
|
/// Usage:
|
|
/// ```dart
|
|
/// if (async.hasError && !async.hasValue) {
|
|
/// return AppErrorView(
|
|
/// error: async.error!,
|
|
/// onRetry: () => ref.invalidate(myProvider),
|
|
/// );
|
|
/// }
|
|
/// ```
|
|
class AppErrorView extends StatelessWidget {
|
|
const AppErrorView({
|
|
super.key,
|
|
required this.error,
|
|
this.title = 'Something went wrong',
|
|
this.onRetry,
|
|
});
|
|
|
|
final Object error;
|
|
|
|
/// Short title shown above the error message.
|
|
final String title;
|
|
|
|
/// When provided, a "Try again" button is rendered below the message.
|
|
final VoidCallback? onRetry;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
final tt = Theme.of(context).textTheme;
|
|
final message = _humanise(error);
|
|
|
|
return Center(
|
|
child: M3FadeSlideIn(
|
|
duration: M3Motion.standard,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
M3BounceIcon(
|
|
icon: Icons.error_outline_rounded,
|
|
iconColor: cs.onErrorContainer,
|
|
backgroundColor: cs.errorContainer,
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
title,
|
|
style: tt.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
message,
|
|
style: tt.bodySmall?.copyWith(color: cs.onSurfaceVariant),
|
|
textAlign: TextAlign.center,
|
|
maxLines: 4,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
if (onRetry != null) ...[
|
|
const SizedBox(height: 24),
|
|
OutlinedButton.icon(
|
|
onPressed: onRetry,
|
|
icon: const Icon(Icons.refresh_rounded),
|
|
label: const Text('Try again'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Strips common Dart exception prefixes so the user sees a clean message.
|
|
static String _humanise(Object error) {
|
|
var text = error.toString();
|
|
const prefixes = ['Exception: ', 'Error: ', 'FormatException: '];
|
|
for (final p in prefixes) {
|
|
if (text.startsWith(p)) {
|
|
text = text.substring(p.length);
|
|
break;
|
|
}
|
|
}
|
|
return text.isEmpty ? 'An unexpected error occurred.' : text;
|
|
}
|
|
}
|
|
|
|
/// A centered empty-state view with an icon, title and optional subtitle.
|
|
///
|
|
/// Usage:
|
|
/// ```dart
|
|
/// if (items.isEmpty && !loading) {
|
|
/// return const AppEmptyView(
|
|
/// icon: Icons.task_outlined,
|
|
/// title: 'No tasks yet',
|
|
/// subtitle: 'Tasks assigned to you will appear here.',
|
|
/// );
|
|
/// }
|
|
/// ```
|
|
class AppEmptyView extends StatelessWidget {
|
|
const AppEmptyView({
|
|
super.key,
|
|
this.icon = Icons.inbox_outlined,
|
|
required this.title,
|
|
this.subtitle,
|
|
this.action,
|
|
});
|
|
|
|
final IconData icon;
|
|
final String title;
|
|
final String? subtitle;
|
|
|
|
/// Optional call-to-action placed below the subtitle.
|
|
final Widget? action;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final cs = Theme.of(context).colorScheme;
|
|
final tt = Theme.of(context).textTheme;
|
|
|
|
return Center(
|
|
child: M3FadeSlideIn(
|
|
duration: M3Motion.standard,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(32),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
M3BounceIcon(
|
|
icon: icon,
|
|
iconColor: cs.onSurfaceVariant,
|
|
backgroundColor: cs.surfaceContainerHighest,
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(
|
|
title,
|
|
style: tt.titleMedium?.copyWith(fontWeight: FontWeight.w600),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
if (subtitle != null) ...[
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
subtitle!,
|
|
style: tt.bodyMedium?.copyWith(color: cs.onSurfaceVariant),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
if (action != null) ...[
|
|
const SizedBox(height: 20),
|
|
action!,
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|