// 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/selected_date_controller.dart'; import '../controllers/today_screen_controller.dart'; import 'scheduler_app_composition.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 implements SchedulerAppComposition { /// 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]. @override String get dateLabel => dateLabelFor(date); /// Initial selected owner-local planning date. @override CivilDate get initialDate => date; /// Default project id used by quick capture. @override String get defaultProjectId => projectId; /// Formats [date] for compact UI date labels. @override String dateLabelFor(CivilDate date) { return formatSchedulerDateLabel(date); } /// Creates the controller that owns the visible planning date. @override SelectedDateController createSelectedDateController() { return SelectedDateController(initialDate: initialDate); } /// 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 = [ 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. @override TodayScreenController createTodayScreenController({ required SelectedDateController selectedDates, }) { return TodayScreenController( selectedDates: selectedDates, dateLabelFor: dateLabelFor, now: () => readAt, read: (date) { 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 createTodayController() { return ApplicationReadController( 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. @override UiReadController createBacklogController() { return ApplicationReadController( 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. @override SchedulerCommandController createCommandController({ required ReadRefresh refreshReads, required SelectedDateReader selectedDate, }) { return SchedulerCommandController( commands: commandUseCases, contextFor: context, selectedDate: selectedDate, refreshReads: refreshReads, quickCaptureProjectId: defaultProjectId, operationIdPrefix: 'ui-command-${readAt.microsecondsSinceEpoch}', ); } /// 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), ); } /// Releases demo composition resources. @override Future dispose() async {} }