focus-flow/apps/focus_flow_flutter/lib/app/demo_scheduler_composition.dart

189 lines
5.8 KiB
Dart

// SPDX-FileCopyrightText: 2026 FocusFlow contributors
// SPDX-License-Identifier: AGPL-3.0-only
/// Composes the FocusFlow Flutter application around Demo Scheduler Composition.
library;
import 'package:scheduler_core/scheduler_core.dart';
import '../controllers/scheduler_command_controller.dart';
import '../controllers/scheduler_read_controller.dart';
import '../controllers/today_screen_controller.dart';
part 'demo/demo_date_formatter.dart';
part 'demo/demo_seed_tasks.dart';
/// Wires the Flutter demo UI to in-memory scheduler application use cases.
class DemoSchedulerComposition {
/// Creates a `DemoSchedulerComposition._` instance with the values required by its invariants.
/// Callers should pass already-normalized scheduler data because validation and defaulting belong at the model or service boundary, not in arbitrary UI code.
DemoSchedulerComposition._({
required this.store,
required this.todayQuery,
required this.managementUseCases,
required this.commandUseCases,
required this.date,
required this.readAt,
});
/// Fixed owner used by the seeded demo data.
static const ownerId = 'owner-1';
/// Fixed owner time zone used by the seeded demo data.
static const timeZoneId = 'UTC';
/// Fixed project id used by the seeded demo data.
static const projectId = 'home';
/// In-memory application store backing the demo.
final InMemoryApplicationUnitOfWork store;
/// Query object used to read the Today state.
final GetTodayStateQuery todayQuery;
/// Management use cases used by backlog-facing UI surfaces.
final V1ApplicationManagementUseCases managementUseCases;
/// Command use cases used by interactive UI controls.
final V1ApplicationCommandUseCases commandUseCases;
/// Local date represented by the seeded demo.
final CivilDate date;
/// Stable read clock used for deterministic demo operations.
final DateTime readAt;
/// Human-readable label for [date].
String get dateLabel => _formatDateLabel(date);
/// Creates a deterministic seeded composition for the demo app and tests.
factory DemoSchedulerComposition.seeded({
Clock? clock,
CivilDate? selectedDate,
bool includeTodayItems = true,
bool includeBacklogItems = true,
}) {
final resolvedDate =
selectedDate ??
CivilDate.fromDateTime((clock ?? const SystemClock()).now());
final readAt = _instant(resolvedDate, 15, 55);
final createdAt = _instant(resolvedDate, 8);
final project = ProjectProfile(
id: projectId,
name: 'Home',
colorKey: 'project-home',
);
final tasks = <Task>[
if (includeTodayItems) ..._todaySeedTasks(resolvedDate, createdAt),
if (includeBacklogItems)
Task.quickCapture(
id: 'later-backlog-note',
title: 'Review reusable grocery list',
projectId: projectId,
createdAt: createdAt,
updatedAt: createdAt,
),
];
final store = InMemoryApplicationUnitOfWork(
initialProjects: [project],
initialOwnerSettings: [
OwnerSettings(
ownerId: ownerId,
timeZoneId: timeZoneId,
compactModeEnabled: true,
),
],
initialTasks: tasks,
);
return DemoSchedulerComposition._(
store: store,
todayQuery: GetTodayStateQuery(
applicationStore: store,
timeZoneResolver: const FixedOffsetTimeZoneResolver.utc(),
),
managementUseCases: V1ApplicationManagementUseCases(
applicationStore: store,
),
commandUseCases: V1ApplicationCommandUseCases(
applicationStore: store,
timeZoneResolver: const FixedOffsetTimeZoneResolver.utc(),
),
date: resolvedDate,
readAt: readAt,
);
}
/// Creates the controller used by the compact Today screen mockup.
TodayScreenController createTodayScreenController() {
return TodayScreenController(
read: () {
return todayQuery.execute(
GetTodayStateRequest(
context: context('ui-plan-1-read-today'),
date: date,
includeNextFlexibleItem: true,
),
);
},
);
}
/// Creates a generic read controller for Today-state panes.
UiReadController<TodayState> createTodayController() {
return ApplicationReadController<TodayState>(
read: () {
return todayQuery.execute(
GetTodayStateRequest(
context: context('ui-read-today'),
date: date,
includeNextFlexibleItem: true,
),
);
},
isEmpty: (state) {
return state.timelineItems.isEmpty && state.pendingNotices.isEmpty;
},
);
}
/// Creates a generic read controller for backlog panes.
UiReadController<BacklogQueryResult> createBacklogController() {
return ApplicationReadController<BacklogQueryResult>(
read: () {
return managementUseCases.getBacklog(
GetBacklogRequest(
context: context('ui-read-backlog'),
sortKey: BacklogSortKey.age,
),
);
},
isEmpty: (state) => state.items.isEmpty,
);
}
/// Creates the command controller used by interactive scheduler controls.
SchedulerCommandController createCommandController({
required ReadRefresh refreshReads,
}) {
return SchedulerCommandController(
commands: commandUseCases,
contextFor: context,
localDate: date,
refreshReads: refreshReads,
);
}
/// Creates an application operation context for the supplied [operationId].
ApplicationOperationContext context(String operationId, {DateTime? now}) {
return ApplicationOperationContext.start(
operationId: operationId,
ownerTimeZone: OwnerTimeZoneContext(
ownerId: ownerId,
timeZoneId: timeZoneId,
),
clock: FixedClock(now ?? readAt),
idGenerator: SequentialIdGenerator(prefix: operationId),
);
}
}