/// 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 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 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 _run( label: 'Completing task', successMessage: 'Task completed', refreshAfterSuccess: true, action: (operationId) { return commands.completeFlexibleTask( context: contextFor(operationId), 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(); } }