focus-flow/apps/focus_flow_flutter/lib/widgets/read_state_view.dart

148 lines
3.9 KiB
Dart

/// Renders the Read State View widget for the FocusFlow Flutter UI.
library;
import 'package:flutter/material.dart';
import '../controllers/scheduler_read_controller.dart';
class ReadStateView<T> extends StatelessWidget {
const ReadStateView({
required this.controller,
required this.title,
required this.emptyTitle,
required this.emptyMessage,
required this.dataBuilder,
super.key,
});
final UiReadController<T> controller;
final String title;
final String emptyTitle;
final String emptyMessage;
final Widget Function(BuildContext context, T value) dataBuilder;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, _) {
return switch (controller.state) {
SchedulerReadLoading<T>() => const Center(
child: CircularProgressIndicator(),
),
SchedulerReadData<T>(value: final value) => dataBuilder(
context,
value,
),
SchedulerReadEmpty<T>(value: final value) => EmptyStatePanel(
title: emptyTitle,
message: emptyMessage,
child: dataBuilder(context, value),
),
SchedulerReadFailure<T>(failure: final failure, error: final error) =>
FailureStatePanel(
title: 'Could not load $title',
code: failure?.detailCode ?? failure?.code.name ?? 'unexpected',
technicalDetail: error?.toString(),
onRetry: controller.retry,
),
};
},
);
}
}
class EmptyStatePanel extends StatelessWidget {
const EmptyStatePanel({
required this.title,
required this.message,
this.child,
super.key,
});
final String title;
final String message;
final Widget? child;
@override
Widget build(BuildContext context) {
final panel = Padding(
padding: const EdgeInsets.all(16),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text(message),
],
),
),
),
);
final content = child;
if (content == null) {
return Center(child: panel);
}
return Column(
children: [
Expanded(child: content),
panel,
],
);
}
}
class FailureStatePanel extends StatelessWidget {
const FailureStatePanel({
required this.title,
required this.code,
required this.onRetry,
this.technicalDetail,
super.key,
});
final String title;
final String code;
final String? technicalDetail;
final Future<void> Function() onRetry;
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 8),
Text('Status: $code'),
if (technicalDetail != null) ...[
const SizedBox(height: 8),
Text(technicalDetail!),
],
const SizedBox(height: 12),
FilledButton.icon(
onPressed: () async {
await onRetry();
},
icon: const Icon(Icons.refresh),
label: const Text('Retry'),
),
],
),
),
),
),
);
}
}