/// Composes the FocusFlow Flutter application around Focus Flow App. library; import 'package:flutter/material.dart'; import '../controllers/today_screen_controller.dart'; import '../models/today_screen_models.dart'; import '../theme/focus_flow_theme.dart'; import '../theme/focus_flow_tokens.dart'; import '../widgets/app_shell.dart'; import '../widgets/required_banner.dart'; import '../widgets/sidebar.dart'; import '../widgets/task_selection_modal.dart'; import '../widgets/timeline/timeline_view.dart'; import '../widgets/top_bar.dart'; import 'demo_scheduler_composition.dart'; /// Root Material app for the FocusFlow desktop UI. class FocusFlowApp extends StatelessWidget { /// Creates a FocusFlow app from an application composition. const FocusFlowApp({required this.composition, super.key}); /// Factory and controller bundle used by the app tree. final DemoSchedulerComposition composition; @override Widget build(BuildContext context) { return MaterialApp( title: 'FocusFlow', debugShowCheckedModeBanner: false, theme: FocusFlowTheme.dark(), home: FocusFlowHome(composition: composition), ); } } /// Stateful home screen that owns the Today controller lifecycle. class FocusFlowHome extends StatefulWidget { /// Creates the FocusFlow home screen from an application composition. const FocusFlowHome({required this.composition, super.key}); /// Factory and controller bundle used by the home screen. final DemoSchedulerComposition composition; @override State createState() => _FocusFlowHomeState(); } class _FocusFlowHomeState extends State { late final TodayScreenController controller = widget.composition .createTodayScreenController(); @override void initState() { super.initState(); controller.load(); } @override void dispose() { controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: FocusFlowTokens.appBackground, body: AppShell( sidebar: const Sidebar(), child: AnimatedBuilder( animation: controller, builder: (context, _) { return switch (controller.state) { TodayScreenLoading() => const Center( child: CircularProgressIndicator(), ), TodayScreenFailure(:final message) => _PlanIssue( message: message, ), TodayScreenEmpty() => _TodayScreenScaffold( data: TodayScreenData.empty( dateLabel: widget.composition.dateLabel, ), selectedCard: controller.selectedCard, onSelectCard: controller.selectCard, onClearSelection: controller.clearSelection, ), TodayScreenReady(:final data) => _TodayScreenScaffold( data: data, selectedCard: controller.selectedCard, onSelectCard: controller.selectCard, onClearSelection: controller.clearSelection, ), }; }, ), ), ); } } class _PlanIssue extends StatelessWidget { const _PlanIssue({required this.message}); final String message; @override Widget build(BuildContext context) { return Center( child: Text(message, style: Theme.of(context).textTheme.titleMedium), ); } } class _TodayScreenScaffold extends StatefulWidget { const _TodayScreenScaffold({ required this.data, required this.selectedCard, required this.onSelectCard, required this.onClearSelection, }); final TodayScreenData data; final TimelineCardModel? selectedCard; final ValueChanged onSelectCard; final VoidCallback onClearSelection; @override State<_TodayScreenScaffold> createState() => _TodayScreenScaffoldState(); } class _TodayScreenScaffoldState extends State<_TodayScreenScaffold> { String? _timelineScrollTargetCardId; var _timelineScrollRequest = 0; void _showUpcomingRequiredTask() { final targetCardId = widget.data.requiredBanner?.timelineCardId; if (targetCardId == null) { return; } setState(() { _timelineScrollTargetCardId = targetCardId; _timelineScrollRequest += 1; }); } @override Widget build(BuildContext context) { return Stack( children: [ Padding( padding: const EdgeInsets.fromLTRB( FocusFlowTokens.mainGutter, 32, FocusFlowTokens.mainGutter, 28, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TopBar(dateLabel: widget.data.dateLabel), const SizedBox(height: 24), RequiredBanner( model: widget.data.requiredBanner, onShowUpcoming: _showUpcomingRequiredTask, ), const SizedBox(height: 22), Expanded( child: TimelineView( cards: widget.data.cards, range: widget.data.timelineRange, scrollTargetCardId: _timelineScrollTargetCardId, scrollRequest: _timelineScrollRequest, onCardSelected: widget.onSelectCard, ), ), ], ), ), if (widget.selectedCard != null) ...[ Positioned.fill( child: GestureDetector( key: const ValueKey('modal-click-away'), behavior: HitTestBehavior.opaque, onTap: widget.onClearSelection, child: const ColoredBox(color: Colors.transparent), ), ), Center( child: TaskSelectionModal( card: widget.selectedCard!, onClose: widget.onClearSelection, ), ), ], ], ); } }