import 'package:flutter/material.dart';
import '../../core/theme/app_theme.dart';
import '../../l10n/app_localizations.dart';
import '../../core/errors/api_error_code.dart';
class LoadingState extends StatelessWidget {
const LoadingState({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return _StateSurface(
icon: Icons.hourglass_empty_outlined,
child: Semantics(
liveRegion: true,
label: l10n.loadingLabel,
child: const CircularProgressIndicator(),
),
);
}
}
class EmptyState extends StatelessWidget {
const EmptyState({super.key, required this.title, required this.body});
final String title;
final String body;
@override
Widget build(BuildContext context) {
return _StateSurface(
icon: Icons.explore_outlined,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
body,
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.palette(context).muted),
),
],
),
);
}
}
class AuthRequiredState extends StatelessWidget {
const AuthRequiredState({super.key, this.title, this.body});
final String? title;
final String? body;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return _StateSurface(
icon: Icons.lock_outline,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title ?? l10n.authRequiredTitle,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
body ?? l10n.authRequiredBody,
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.palette(context).muted),
),
],
),
);
}
}
class ErrorState extends StatelessWidget {
const ErrorState({super.key, required this.onRetry, this.code});
final VoidCallback onRetry;
final ApiErrorCode? code;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return _StateSurface(
icon: Icons.cloud_off_outlined,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.errorTitle, style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 8),
Text(
_localizedBody(l10n),
textAlign: TextAlign.center,
style: TextStyle(color: AppTheme.palette(context).muted),
),
const SizedBox(height: 20),
FilledButton(onPressed: onRetry, child: Text(l10n.retry)),
],
),
);
}
String _localizedBody(AppLocalizations l10n) {
return switch (code) {
ApiErrorCode.networkUnavailable => l10n.errorNetworkUnavailable,
ApiErrorCode.timeout => l10n.errorTimeout,
ApiErrorCode.notFound => l10n.errorNotFound,
ApiErrorCode.permissionDenied => l10n.errorPermissionDenied,
ApiErrorCode.invalidResponse => l10n.errorInvalidResponse,
ApiErrorCode.serviceUnavailable => l10n.errorServiceUnavailable,
ApiErrorCode.conflict => l10n.errorConflict,
ApiErrorCode.rateLimited => l10n.errorRateLimited,
ApiErrorCode.validation => l10n.errorValidation,
ApiErrorCode.unknown || null => l10n.errorBody,
};
}
}
class _StateSurface extends StatelessWidget {
const _StateSurface({required this.icon, required this.child});
final IconData icon;
final Widget child;
@override
Widget build(BuildContext context) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 520),
child: SingleChildScrollView(
child: Card(
margin: const EdgeInsets.all(20),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 42, color: AppTheme.palette(context).muted),
const SizedBox(height: 12),
child,
],
),
),
),
),
),
);
}
}
