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

205 lines
5.8 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}