focus-flow/apps/focus_flow_flutter/lib/controllers/scheduler_command_controller.dart

155 lines
4 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';
typedef OperationContextFactory =
ApplicationOperationContext Function(String operationId);
typedef ReadRefresh = Future<void> Function();
sealed class SchedulerCommandState {
const SchedulerCommandState();
}
class SchedulerCommandIdle extends SchedulerCommandState {
const SchedulerCommandIdle();
}
class SchedulerCommandRunning extends SchedulerCommandState {
const SchedulerCommandRunning(this.label);
final String label;
}
class SchedulerCommandSuccess extends SchedulerCommandState {
const SchedulerCommandSuccess(this.message);
final String message;
}
class SchedulerCommandFailure extends SchedulerCommandState {
const SchedulerCommandFailure({this.failure, this.error});
final ApplicationFailure? failure;
final Object? error;
String get codeLabel {
final typed = failure;
if (typed != null) {
return typed.detailCode ?? typed.code.name;
}
return 'unexpected';
}
}
class SchedulerCommandController extends ChangeNotifier {
SchedulerCommandController({
required this.commands,
required this.contextFor,
required this.localDate,
required this.refreshReads,
});
final V1ApplicationCommandUseCases commands;
final OperationContextFactory contextFor;
final CivilDate localDate;
final ReadRefresh refreshReads;
var _sequence = 0;
SchedulerCommandState _state = const SchedulerCommandIdle();
SchedulerCommandState get state => _state;
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',
);
},
);
}
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,
);
},
);
}
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();
}
}