186 lines
5.5 KiB
Dart
186 lines
5.5 KiB
Dart
/// Coordinates Scheduler Command Controller state for the FocusFlow Flutter UI.
|
|
library;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:scheduler_core/scheduler_core.dart';
|
|
|
|
/// Builds an application operation context for a UI command operation.
|
|
typedef OperationContextFactory =
|
|
ApplicationOperationContext Function(String operationId);
|
|
|
|
/// Refreshes any reads that should reflect a completed command.
|
|
typedef ReadRefresh = Future<void> Function();
|
|
|
|
/// Base state for scheduler command execution in the UI.
|
|
sealed class SchedulerCommandState {
|
|
/// Creates a scheduler command state.
|
|
const SchedulerCommandState();
|
|
}
|
|
|
|
/// Indicates that no scheduler command is currently running.
|
|
class SchedulerCommandIdle extends SchedulerCommandState {
|
|
/// Creates an idle command state.
|
|
const SchedulerCommandIdle();
|
|
}
|
|
|
|
/// Indicates that a scheduler command is currently running.
|
|
class SchedulerCommandRunning extends SchedulerCommandState {
|
|
/// Creates a running command state with a user-facing [label].
|
|
const SchedulerCommandRunning(this.label);
|
|
|
|
/// User-facing description of the command in progress.
|
|
final String label;
|
|
}
|
|
|
|
/// Indicates that a scheduler command completed successfully.
|
|
class SchedulerCommandSuccess extends SchedulerCommandState {
|
|
/// Creates a success command state with a user-facing [message].
|
|
const SchedulerCommandSuccess(this.message);
|
|
|
|
/// User-facing success message.
|
|
final String message;
|
|
}
|
|
|
|
/// Indicates that a scheduler command failed.
|
|
class SchedulerCommandFailure extends SchedulerCommandState {
|
|
/// Creates a failure state from an application [failure] or unexpected [error].
|
|
const SchedulerCommandFailure({this.failure, this.error});
|
|
|
|
/// Typed application failure returned by the scheduler core.
|
|
final ApplicationFailure? failure;
|
|
|
|
/// Unexpected error thrown while running the command.
|
|
final Object? error;
|
|
|
|
/// Stable label for display and diagnostics.
|
|
String get codeLabel {
|
|
final typed = failure;
|
|
if (typed != null) {
|
|
return typed.detailCode ?? typed.code.name;
|
|
}
|
|
return 'unexpected';
|
|
}
|
|
}
|
|
|
|
/// Runs scheduler write commands and exposes command state to widgets.
|
|
class SchedulerCommandController extends ChangeNotifier {
|
|
/// Creates a command controller from scheduler use cases and UI callbacks.
|
|
SchedulerCommandController({
|
|
required this.commands,
|
|
required this.contextFor,
|
|
required this.localDate,
|
|
required this.refreshReads,
|
|
});
|
|
|
|
/// Scheduler write use cases invoked by this controller.
|
|
final V1ApplicationCommandUseCases commands;
|
|
|
|
/// Factory for command-scoped application operation contexts.
|
|
final OperationContextFactory contextFor;
|
|
|
|
/// Local date commands should operate against.
|
|
final CivilDate localDate;
|
|
|
|
/// Refresh callback invoked after commands that mutate read state.
|
|
final ReadRefresh refreshReads;
|
|
var _sequence = 0;
|
|
SchedulerCommandState _state = const SchedulerCommandIdle();
|
|
|
|
/// Current command execution state.
|
|
SchedulerCommandState get state => _state;
|
|
|
|
/// Captures [title] as a backlog task.
|
|
Future<void> quickCaptureToBacklog(String title) async {
|
|
await _run(
|
|
label: 'Capturing task',
|
|
successMessage: 'Task captured',
|
|
refreshAfterSuccess: true,
|
|
action: (operationId) {
|
|
return commands.quickCaptureToBacklog(
|
|
context: contextFor(operationId),
|
|
title: title,
|
|
projectId: 'home',
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Schedules a backlog task into the next available slot.
|
|
Future<void> scheduleBacklogItem({
|
|
required String taskId,
|
|
required int durationMinutes,
|
|
required DateTime expectedUpdatedAt,
|
|
}) async {
|
|
await _run(
|
|
label: 'Scheduling task',
|
|
successMessage: 'Task scheduled',
|
|
refreshAfterSuccess: true,
|
|
action: (operationId) {
|
|
return commands.scheduleBacklogItemToNextAvailableSlot(
|
|
context: contextFor(operationId),
|
|
localDate: localDate,
|
|
taskId: taskId,
|
|
durationMinutes: durationMinutes,
|
|
expectedUpdatedAt: expectedUpdatedAt,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Completes a planned flexible task from the Today view.
|
|
Future<void> completeFlexibleTask({
|
|
required String taskId,
|
|
DateTime? expectedUpdatedAt,
|
|
}) async {
|
|
await _run(
|
|
label: 'Completing task',
|
|
successMessage: 'Task completed',
|
|
refreshAfterSuccess: true,
|
|
action: (operationId) {
|
|
return commands.completeFlexibleTask(
|
|
context: contextFor(operationId),
|
|
localDate: localDate,
|
|
taskId: taskId,
|
|
expectedUpdatedAt: expectedUpdatedAt,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> _run({
|
|
required String label,
|
|
required String successMessage,
|
|
required bool refreshAfterSuccess,
|
|
required Future<ApplicationResult<ApplicationCommandResult>> Function(
|
|
String operationId,
|
|
)
|
|
action,
|
|
}) async {
|
|
final operationId = _nextOperationId();
|
|
_setState(SchedulerCommandRunning(label));
|
|
try {
|
|
final result = await action(operationId);
|
|
final failure = result.failure;
|
|
if (failure != null) {
|
|
_setState(SchedulerCommandFailure(failure: failure));
|
|
return;
|
|
}
|
|
if (refreshAfterSuccess) {
|
|
await refreshReads();
|
|
}
|
|
_setState(SchedulerCommandSuccess(successMessage));
|
|
} on Object catch (error) {
|
|
_setState(SchedulerCommandFailure(error: error));
|
|
}
|
|
}
|
|
|
|
String _nextOperationId() {
|
|
_sequence += 1;
|
|
return 'ui-command-$_sequence';
|
|
}
|
|
|
|
void _setState(SchedulerCommandState next) {
|
|
_state = next;
|
|
notifyListeners();
|
|
}
|
|
}
|