Merges 20 commits from Ashley's smolblocks.com instance (main branch), bringing in the full Backlog Board screen (BacklogBoardController, board columns/cards, task detail drawer, summary panel, search/filter/sort/group, Break Up and Someday actions) plus the unified FocusFlowSection navigation model. Reconciled with our own in-flight work: - Replaced our placeholder BacklogPane/createBacklogController wiring with Ashley's real BacklogBoardScreen/BacklogBoardController (same problem, more complete implementation - avoids two competing Backlog UIs). - Adopted the FocusFlowSection-based Sidebar API in place of our bespoke SidebarScreen enum; the Settings nav item now intercepts FocusFlowSection.settings in _selectSection to open our Settings dialog instead of falling through to a placeholder screen. - Gave every sidebar nav item a stable key (not just the active one) so the Settings dialog tests can still target it reliably. - Kept our additive owner-settings read controller, Settings dialog, and timeline Break-up wiring; renamed the Today controller field to todayController throughout to match upstream's now-multi-controller naming. - Fixed a test-only regression: backlog_board_screen_test.dart's standalone SchedulerCommandController construction needed the new `management` param. - Added .gitleaks.toml allowlisting the local, gitignored .claude/settings.local.json path (recorded bash command patterns, not repo secrets) that was blocking commits. Verified via focusflow-flutter-ci:3.44.4 (Flutter 3.44.4/Dart 3.12.2): flutter analyze clean, dart format clean, 106/106 Flutter tests passing, 348/348 scheduler_core + scheduler_persistence_sqlite tests passing. Closes: Circuit-Forge/focus-flow#9
248 lines
7.8 KiB
Dart
248 lines
7.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/backlog_board_controller.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 projects = _demoProjects();
|
|
final tasks = <Task>[
|
|
if (includeTodayItems) ..._todaySeedTasks(resolvedDate, createdAt),
|
|
if (includeBacklogItems) ..._backlogSeedTasks(resolvedDate, createdAt),
|
|
];
|
|
final store = InMemoryApplicationUnitOfWork(
|
|
initialProjects: projects,
|
|
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 the controller used by the Backlog board screen.
|
|
@override
|
|
BacklogBoardController createBacklogBoardController({
|
|
required SelectedDateController selectedDates,
|
|
}) {
|
|
return BacklogBoardController(
|
|
selectedDates: selectedDates,
|
|
dateLabelFor: dateLabelFor,
|
|
readBoard: (request) {
|
|
return managementUseCases.getBacklogBoard(
|
|
GetBacklogBoardRequest(
|
|
context: context('ui-read-backlog-board'),
|
|
localDate: request.localDate,
|
|
searchText: request.searchText,
|
|
filters: request.filters,
|
|
boardFilters: request.boardFilters,
|
|
sortKey: request.sortKey,
|
|
groupMode: request.groupMode,
|
|
),
|
|
);
|
|
},
|
|
readTaskDetail: (taskId, localDate) {
|
|
return managementUseCases.getBacklogTaskDetail(
|
|
GetBacklogTaskDetailRequest(
|
|
context: context('ui-read-backlog-detail'),
|
|
localDate: localDate,
|
|
taskId: taskId,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// 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 the settings screen.
|
|
@override
|
|
UiReadController<OwnerSettings> createOwnerSettingsController() {
|
|
return ApplicationReadController<OwnerSettings>(
|
|
read: () {
|
|
return managementUseCases.getOwnerSettings(
|
|
GetOwnerSettingsRequest(context: context('ui-read-owner-settings')),
|
|
);
|
|
},
|
|
isEmpty: (_) => false,
|
|
);
|
|
}
|
|
|
|
/// Creates the command controller used by interactive scheduler controls.
|
|
@override
|
|
SchedulerCommandController createCommandController({
|
|
required ReadRefresh refreshReads,
|
|
required SelectedDateReader selectedDate,
|
|
}) {
|
|
return SchedulerCommandController(
|
|
commands: commandUseCases,
|
|
management: managementUseCases,
|
|
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<void> dispose() async {}
|
|
}
|