/// 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'; /// Wires the Flutter demo UI to in-memory scheduler application use cases. class DemoSchedulerComposition { 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 = [ 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 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. 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. 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) { return ApplicationOperationContext.start( operationId: operationId, ownerTimeZone: OwnerTimeZoneContext( ownerId: ownerId, timeZoneId: timeZoneId, ), clock: FixedClock(readAt), idGenerator: SequentialIdGenerator(prefix: operationId), ); } /// Formats a civil date as a long month/day/year label. static String formatDateLabel(CivilDate date) { const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; return '${months[date.month - 1]} ${date.day}, ${date.year}'; } static List _todaySeedTasks(CivilDate date, DateTime createdAt) { return [ _task( id: 'clean-coffee-maker', title: 'Clean coffee maker', type: TaskType.flexible, status: TaskStatus.planned, date: date, startHour: 16, startMinute: 15, endHour: 16, endMinute: 30, reward: RewardLevel.medium, difficulty: DifficultyLevel.easy, createdAt: createdAt, ), _task( id: 'cleaned-kitchen-counter-early', title: 'Cleaned kitchen counter', type: TaskType.surprise, status: TaskStatus.completed, date: date, startHour: 16, startMinute: 45, endHour: 17, endMinute: 0, reward: RewardLevel.medium, difficulty: DifficultyLevel.medium, completedAt: _instant(date, 16, 20), createdAt: createdAt, ), _task( id: 'sort-mail', title: 'Sort mail', type: TaskType.flexible, status: TaskStatus.planned, date: date, startHour: 14, startMinute: 40, endHour: 15, endMinute: 20, reward: RewardLevel.low, difficulty: DifficultyLevel.easy, createdAt: createdAt, ), _task( id: 'start-laundry', title: 'Start laundry', type: TaskType.flexible, status: TaskStatus.planned, date: date, startHour: 15, startMinute: 0, endHour: 15, endMinute: 40, reward: RewardLevel.medium, difficulty: DifficultyLevel.medium, createdAt: createdAt, ), _task( id: 'water-plants', title: 'Water plants', type: TaskType.flexible, status: TaskStatus.planned, date: date, startHour: 15, startMinute: 20, endHour: 16, endMinute: 0, reward: RewardLevel.medium, difficulty: DifficultyLevel.medium, createdAt: createdAt, ), _task( id: 'pay-bill', title: 'Pay bill', type: TaskType.critical, status: TaskStatus.planned, date: date, startHour: 18, startMinute: 0, endHour: 18, endMinute: 10, reward: RewardLevel.low, difficulty: DifficultyLevel.medium, createdAt: createdAt, ), _task( id: 'doctor-appointment', title: 'Doctor appointment', type: TaskType.inflexible, status: TaskStatus.planned, date: date, startHour: 18, startMinute: 30, endHour: 19, endMinute: 15, reward: RewardLevel.medium, difficulty: DifficultyLevel.hard, createdAt: createdAt, ), _task( id: 'free-slot', title: 'Free Slot', type: TaskType.freeSlot, status: TaskStatus.planned, date: date, startHour: 19, startMinute: 15, endHour: 20, endMinute: 15, reward: RewardLevel.notSet, difficulty: DifficultyLevel.notSet, createdAt: createdAt, ), _task( id: 'cleaned-kitchen-counter-late', title: 'Cleaned kitchen counter', type: TaskType.surprise, status: TaskStatus.completed, date: date, startHour: 20, startMinute: 15, endHour: 20, endMinute: 30, reward: RewardLevel.medium, difficulty: DifficultyLevel.medium, completedAt: _instant(date, 20, 20), createdAt: createdAt, ), ]; } static Task _task({ required String id, required String title, required TaskType type, required TaskStatus status, required CivilDate date, required int startHour, required int startMinute, required int endHour, required int endMinute, required RewardLevel reward, required DifficultyLevel difficulty, required DateTime createdAt, DateTime? completedAt, }) { final start = _instant(date, startHour, startMinute); final end = _instant(date, endHour, endMinute); return Task( id: id, title: title, projectId: projectId, type: type, status: status, priority: PriorityLevel.medium, reward: reward, difficulty: difficulty, durationMinutes: end.difference(start).inMinutes, scheduledStart: start, scheduledEnd: end, actualStart: status == TaskStatus.completed ? start : null, actualEnd: status == TaskStatus.completed ? end : null, completedAt: completedAt, createdAt: createdAt, updatedAt: completedAt ?? createdAt, ); } static DateTime _instant(CivilDate date, int hour, [int minute = 0]) { return DateTime.utc(date.year, date.month, date.day, hour, minute); } }