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

144 lines
4.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:scheduler_core/scheduler_core.dart';
import '../controllers/scheduler_command_controller.dart';
import '../controllers/scheduler_read_controller.dart';
import 'read_state_view.dart';
import 'schedule_components.dart';
class BacklogPane extends StatelessWidget {
const BacklogPane({
required this.controller,
this.commandController,
super.key,
});
final UiReadController<BacklogQueryResult> controller;
final SchedulerCommandController? commandController;
@override
Widget build(BuildContext context) {
return ReadStateView<BacklogQueryResult>(
controller: controller,
title: 'Backlog',
emptyTitle: 'Backlog is clear',
emptyMessage: 'Captured tasks will appear here when they need planning.',
dataBuilder: (context, result) =>
BacklogContent(result: result, commandController: commandController),
);
}
}
class BacklogContent extends StatelessWidget {
const BacklogContent({
required this.result,
this.commandController,
super.key,
});
final BacklogQueryResult result;
final SchedulerCommandController? commandController;
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(16),
children: [
Text('Backlog', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 12),
if (commandController != null) ...[
QuickCaptureForm(commandController: commandController!),
const SizedBox(height: 12),
CommandStatusBanner(controller: commandController!),
const SizedBox(height: 12),
],
for (final item in result.items)
BacklogRow(
item: item,
onSchedule: commandController == null
? null
: (durationMinutes) {
return commandController!.scheduleBacklogItem(
taskId: item.task.id,
durationMinutes: durationMinutes,
expectedUpdatedAt: item.task.updatedAt,
);
},
),
],
);
}
}
class QuickCaptureForm extends StatefulWidget {
const QuickCaptureForm({required this.commandController, super.key});
final SchedulerCommandController commandController;
@override
State<QuickCaptureForm> createState() => _QuickCaptureFormState();
}
class _QuickCaptureFormState extends State<QuickCaptureForm> {
final titleController = TextEditingController();
@override
void dispose() {
titleController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: TextField(
key: const ValueKey('quick-capture-title'),
controller: titleController,
decoration: const InputDecoration(labelText: 'Quick capture'),
textInputAction: TextInputAction.done,
),
),
const SizedBox(width: 8),
FilledButton.icon(
onPressed: () async {
final title = titleController.text.trim();
if (title.isEmpty) {
return;
}
await widget.commandController.quickCaptureToBacklog(title);
titleController.clear();
},
icon: const Icon(Icons.add),
label: const Text('Capture'),
),
],
);
}
}
class CommandStatusBanner extends StatelessWidget {
const CommandStatusBanner({required this.controller, super.key});
final SchedulerCommandController controller;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
builder: (context, _) {
return switch (controller.state) {
SchedulerCommandIdle() => const SizedBox.shrink(),
SchedulerCommandRunning(label: final label) => Text(label),
SchedulerCommandSuccess(message: final message) => Text(message),
SchedulerCommandFailure(failure: final failure, error: final error) =>
Text(
'Command issue: '
'${failure?.detailCode ?? failure?.code.name ?? error ?? 'unexpected'}',
),
};
},
);
}
}