// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Renders the Schedule Components widget for the FocusFlow Flutter UI. library; import 'package:flutter/material.dart'; import 'package:scheduler_core/scheduler_core.dart'; import '../theme/scheduler_visual_tokens.dart'; /// Compact summary panel for timeline items. class CompactPanel extends StatelessWidget { /// Creates a compact panel for [items]. const CompactPanel({required this.items, super.key}); /// Timeline items to display in compact form. final List items; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { if (items.isEmpty) { return const SizedBox.shrink(); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Compact', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), for (final item in items) TimelineRow(item: item), const SizedBox(height: 20), ], ); } } /// Card row for a single Today timeline item. class TimelineRow extends StatelessWidget { /// Creates a timeline row. const TimelineRow({required this.item, this.onDone, super.key}); /// Timeline item to render. final TodayTimelineItem item; /// Optional completion callback for eligible tasks. final Future Function()? onDone; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final projectColor = SchedulerVisualTokens.projectColor( item.projectColorToken, ); return Card( margin: const EdgeInsets.only(bottom: 8), color: SchedulerVisualTokens.backgroundColor( item.item.backgroundToken, scheme, ), shape: RoundedRectangleBorder( side: BorderSide(color: projectColor, width: 2), borderRadius: BorderRadius.circular(8), ), child: ListTile( leading: Icon( SchedulerVisualTokens.taskIcon(item.taskType), color: projectColor, ), title: Text(item.item.displayTitle), subtitle: Text(timeText(item.item)), trailing: Wrap( spacing: 6, children: [ Icon( SchedulerVisualTokens.rewardIcon(item.item.rewardIconToken), size: 18, ), Icon( SchedulerVisualTokens.difficultyIcon( item.item.difficultyIconToken, ), size: 18, ), if (onDone != null) IconButton( tooltip: 'Done', onPressed: () { onDone!(); }, icon: const Icon(Icons.check_circle_outline), ), ], ), ), ); } } /// Backlog row with optional scheduling controls. class BacklogRow extends StatefulWidget { /// Creates a backlog row. const BacklogRow({required this.item, this.onSchedule, super.key}); /// Backlog item to render. final BacklogItemReadModel item; /// Optional callback that schedules the item for a duration in minutes. final Future Function(int durationMinutes)? onSchedule; /// Creates the mutable state object used by this stateful widget. /// Flutter calls this once for each mounted widget instance before lifecycle callbacks and builds begin. @override State createState() => _BacklogRowState(); } /// Private implementation type for `_BacklogRowState` in this library. /// It keeps supporting state and behavior scoped to the owning file so the public API stays focused on the scheduler concepts that callers use directly. class _BacklogRowState extends State { /// Stores the `durationController` value for this object. /// This field is part of the explicit state that downstream scheduling, persistence, or UI code reads when composing behavior. final durationController = TextEditingController(); /// Releases resources owned by this object before it leaves the tree. /// Controllers, listeners, and other disposable collaborators should be cleaned up here to avoid stale callbacks. @override void dispose() { durationController.dispose(); super.dispose(); } /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: StalenessMarker(marker: widget.item.stalenessMarker), title: Text(widget.item.task.title), subtitle: Text( 'Priority ${widget.item.task.priority?.name ?? 'unset'}', ), iconColor: SchedulerVisualTokens.stalenessColor( widget.item.stalenessMarker, scheme, ), trailing: widget.onSchedule == null ? null : SizedBox( width: 168, child: Row( children: [ Expanded( child: TextField( key: ValueKey('duration-${widget.item.task.id}'), controller: durationController, decoration: const InputDecoration(labelText: 'Min'), keyboardType: TextInputType.number, ), ), IconButton( tooltip: 'Schedule', onPressed: () { final duration = int.tryParse( durationController.text.trim(), ); if (duration == null) { return; } widget.onSchedule!(duration); }, icon: const Icon(Icons.event_available), ), ], ), ), ), ); } } /// Icon marker for backlog item staleness. class StalenessMarker extends StatelessWidget { /// Creates a staleness marker. const StalenessMarker({required this.marker, super.key}); /// Staleness marker value to render. final BacklogStalenessMarker marker; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { return Icon( SchedulerVisualTokens.stalenessIcon(marker), semanticLabel: 'Staleness ${marker.name}', ); } } /// Notice banner for pending Today-state messages. class NoticeBanner extends StatelessWidget { /// Creates a notice banner. const NoticeBanner({required this.message, super.key}); /// Notice message to display. final String message; /// Builds the widget subtree for this component. /// The method translates the current widget configuration and state into Flutter UI without mutating scheduler domain data. @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondaryContainer, borderRadius: BorderRadius.circular(8), ), child: Text(message), ); } } /// Formats a scheduler timeline item as display-ready time text. String timeText(TimelineItem item) { final start = item.start; final end = item.end; if (start == null || end == null) { return '${item.durationMinutes ?? 0} min'; } return '${_clockText(start)}-${_clockText(end)}'; } /// Top-level helper that performs the `_clockText` operation for this file. /// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. String _clockText(DateTime value) { final hour = value.hour.toString().padLeft(2, '0'); final minute = value.minute.toString().padLeft(2, '0'); return '$hour:$minute'; }