/// 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, {DateTime? now}); /// Refreshes any reads that should reflect a completed command. typedef ReadRefresh = Future 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, DateTime Function()? now, }) : now = now ?? _utcNow; /// 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; /// Clock used by direct UI commands such as marking a task complete. final DateTime Function() now; var _sequence = 0; SchedulerCommandState _state = const SchedulerCommandIdle(); /// Current command execution state. SchedulerCommandState get state => _state; /// Captures [title] as a backlog task. Future 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 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 completeFlexibleTask({ required String taskId, DateTime? expectedUpdatedAt, }) async { await completeTask( taskId: taskId, taskType: TaskType.flexible, expectedUpdatedAt: expectedUpdatedAt, ); } /// Completes a planned task from the Today timeline. Future completeTask({ required String taskId, required TaskType taskType, DateTime? expectedUpdatedAt, }) async { final completedAt = now(); await _run( label: 'Completing task', successMessage: 'Task completed', refreshAfterSuccess: true, action: (operationId) { final context = contextFor(operationId, now: completedAt); return switch (taskType) { TaskType.flexible => commands.completeFlexibleTask( context: context, localDate: localDate, taskId: taskId, expectedUpdatedAt: expectedUpdatedAt, ), TaskType.critical || TaskType.inflexible => commands.applyRequiredTaskAction( context: context, localDate: localDate, taskId: taskId, action: RequiredTaskAction.done, expectedUpdatedAt: expectedUpdatedAt, ), TaskType.locked || TaskType.surprise || TaskType.freeSlot => Future.value( ApplicationResult.failure( ApplicationFailure( code: ApplicationFailureCode.validation, entityId: taskId, detailCode: 'unsupportedTaskType', ), ), ), }; }, ); } /// Returns a completed task to its planned state. Future uncompleteTask({ required String taskId, DateTime? expectedUpdatedAt, }) async { final occurredAt = now(); await _run( label: 'Reopening task', successMessage: 'Task marked as not done', refreshAfterSuccess: true, action: (operationId) { return commands.uncompleteTask( context: contextFor(operationId, now: occurredAt), localDate: localDate, taskId: taskId, expectedUpdatedAt: expectedUpdatedAt, ); }, ); } Future _run({ required String label, required String successMessage, required bool refreshAfterSuccess, required Future> 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(); } static DateTime _utcNow() => DateTime.now().toUtc(); }