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

130 lines
4.1 KiB
Dart

// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only
/// Coordinates Scheduler Read Controller state for the FocusFlow Flutter UI.
library;
import 'package:flutter/foundation.dart';
import 'package:scheduler_core/scheduler_core.dart';
/// Reads an application value for a UI surface.
typedef ApplicationReader<T> = Future<ApplicationResult<T>> Function();
/// Determines whether a successful read should be rendered as empty state.
typedef EmptyPredicate<T> = bool Function(T value);
/// Shared interface for read controllers consumed by UI widgets.
abstract class UiReadController<T> extends ChangeNotifier {
/// Current read state.
SchedulerReadState<T> get state;
/// Loads the backing value.
Future<void> load();
/// Retries the latest read.
Future<void> retry();
}
/// Base state for scheduler read operations.
sealed class SchedulerReadState<T> {
/// Creates a scheduler read state.
const SchedulerReadState();
}
/// Indicates that a scheduler read is in progress.
class SchedulerReadLoading<T> extends SchedulerReadState<T> {
/// Creates a loading read state.
const SchedulerReadLoading();
}
/// Indicates that a scheduler read returned renderable data.
class SchedulerReadData<T> extends SchedulerReadState<T> {
/// Creates a data state from [value].
const SchedulerReadData(this.value);
/// Value returned by the read operation.
final T value;
}
/// Indicates that a scheduler read succeeded but has no primary content.
class SchedulerReadEmpty<T> extends SchedulerReadState<T> {
/// Creates an empty state that still carries the read [value].
const SchedulerReadEmpty(this.value);
/// Value returned by the read operation.
final T value;
}
/// Indicates that a scheduler read failed.
class SchedulerReadFailure<T> extends SchedulerReadState<T> {
/// Creates a failure state from an application [failure] or unexpected [error].
const SchedulerReadFailure({this.failure, this.error});
/// Typed application failure returned by the scheduler core.
final ApplicationFailure? failure;
/// Unexpected error thrown while running the read.
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';
}
}
/// Default read controller backed by an [ApplicationReader].
class ApplicationReadController<T> extends UiReadController<T> {
/// Creates a controller from a read callback and empty-state predicate.
ApplicationReadController({required this.read, required this.isEmpty});
/// Callback used to load the application value.
final ApplicationReader<T> read;
/// Predicate used to classify a successful value as empty or populated.
final EmptyPredicate<T> isEmpty;
/// Private state stored as `_state` for this implementation.
/// The field is intentionally scoped to this library so only the owning type can change the related scheduler or UI behavior.
SchedulerReadState<T> _state = SchedulerReadLoading<T>();
/// Current read state.
@override
SchedulerReadState<T> get state => _state;
/// Loads the current value and updates [state].
@override
Future<void> load() async {
_setState(SchedulerReadLoading<T>());
try {
final result = await read();
final failure = result.failure;
if (failure != null) {
_setState(SchedulerReadFailure<T>(failure: failure));
return;
}
final value = result.requireValue;
_setState(
isEmpty(value)
? SchedulerReadEmpty<T>(value)
: SchedulerReadData<T>(value),
);
} on Object catch (error) {
_setState(SchedulerReadFailure<T>(error: error));
}
}
/// Retries by running [load] again.
@override
Future<void> retry() => load();
/// Runs the `_setState` helper used inside this library.
/// The helper centralizes a small piece of transformation or validation so higher-level scheduler flow remains easier to read.
void _setState(SchedulerReadState<T> next) {
_state = next;
notifyListeners();
}
}