ponshu-room-lite/lib/widgets/common/error_retry_widget.dart

205 lines
5.8 KiB
Dart
Raw Permalink Normal View History

import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../theme/app_colors.dart';
/// エラー発生時に表示する再試行可能なウィジェット
///
/// Riverpod の AsyncValue.error と組み合わせて使用:
/// ```dart
/// asyncValue.when(
/// data: (data) => YourWidget(data),
/// loading: () => const CircularProgressIndicator(),
/// error: (err, stack) => ErrorRetryWidget(
/// message: 'データの読み込みに失敗しました',
/// details: err.toString(),
/// onRetry: () => ref.refresh(yourProvider),
/// ),
/// )
/// ```
class ErrorRetryWidget extends StatelessWidget {
/// エラーメッセージ(ユーザー向けの説明)
final String message;
/// 再試行ボタンが押されたときのコールバック
final VoidCallback onRetry;
/// エラーの詳細情報(デバッグ用、オプション)
final String? details;
/// ウィジェットを中央配置するか(デフォルト: true
/// false の場合は親ウィジェットのレイアウトに従う
final bool centered;
/// アイコンのサイズ
final double iconSize;
/// コンパクト表示(パディングを小さくする)
final bool compact;
const ErrorRetryWidget({
super.key,
required this.message,
required this.onRetry,
this.details,
this.centered = true,
this.iconSize = 48,
this.compact = false,
});
@override
Widget build(BuildContext context) {
final appColors = Theme.of(context).extension<AppColors>()!;
final padding = compact
? const EdgeInsets.all(16)
: const EdgeInsets.symmetric(horizontal: 24, vertical: 32);
final content = Padding(
padding: padding,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// エラーアイコン
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: appColors.error.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(
LucideIcons.alertCircle,
size: iconSize,
color: appColors.error,
),
),
SizedBox(height: compact ? 12 : 16),
// エラーメッセージ
Text(
message,
style: TextStyle(
fontSize: compact ? 14 : 16,
fontWeight: FontWeight.w600,
color: appColors.textPrimary,
),
textAlign: TextAlign.center,
),
// 詳細情報(存在する場合)
if (details != null) ...[
SizedBox(height: compact ? 6 : 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: appColors.surfaceSubtle,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: appColors.divider),
),
child: Text(
details!,
style: TextStyle(
fontSize: 12,
color: appColors.textSecondary,
fontFamily: 'monospace',
),
textAlign: TextAlign.center,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
SizedBox(height: compact ? 16 : 24),
// 再試行ボタン
ElevatedButton.icon(
onPressed: onRetry,
icon: const Icon(LucideIcons.refreshCw, size: 20),
label: const Text('再試行'),
style: ElevatedButton.styleFrom(
backgroundColor: appColors.brandPrimary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
);
return centered
? Center(child: content)
: content;
}
}
/// シンプルなエラー表示ウィジェット(再試行ボタンなし)
///
/// 再試行が不要な場合や、別の方法で対処する場合に使用
class ErrorDisplayWidget extends StatelessWidget {
final String message;
final String? details;
final bool centered;
final double iconSize;
final bool compact;
const ErrorDisplayWidget({
super.key,
required this.message,
this.details,
this.centered = true,
this.iconSize = 48,
this.compact = false,
});
@override
Widget build(BuildContext context) {
final appColors = Theme.of(context).extension<AppColors>()!;
final padding = compact
? const EdgeInsets.all(16)
: const EdgeInsets.symmetric(horizontal: 24, vertical: 32);
final content = Padding(
padding: padding,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
LucideIcons.alertCircle,
size: iconSize,
color: appColors.error,
),
SizedBox(height: compact ? 12 : 16),
Text(
message,
style: TextStyle(
fontSize: compact ? 14 : 16,
color: appColors.textPrimary,
),
textAlign: TextAlign.center,
),
if (details != null) ...[
SizedBox(height: compact ? 6 : 8),
Text(
details!,
style: TextStyle(
fontSize: 12,
color: appColors.textSecondary,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
);
return centered
? Center(child: content)
: content;
}
}