// SPDX-FileCopyrightText: 2026 FocusFlow contributors // SPDX-License-Identifier: AGPL-3.0-only /// Renders the Task Selection Modal widget for the FocusFlow Flutter UI. library; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:scheduler_core/scheduler_core.dart'; import '../models/today_screen_models.dart'; import '../theme/focus_flow_tokens.dart'; import 'dialogs/break_up_dialog.dart'; import 'status_circle_button.dart'; import 'timeline/difficulty_bars.dart'; import 'timeline/reward_icon.dart'; part 'task_selection/action_button.dart'; part 'task_selection/header_metadata.dart'; part 'task_selection/info_tile.dart'; part 'task_selection/modal_accent.dart'; part 'task_selection/modal_status_button.dart'; /// Modal shown when a selectable timeline task card is opened. class TaskSelectionModal extends StatelessWidget { /// Creates a task selection modal for [card]. const TaskSelectionModal({ required this.card, required this.onClose, this.onStatusPressed, this.onPushToNext, this.onPushToTomorrow, this.onPushToBacklog, this.onRemove, this.onBreakUp, super.key, }); /// Card model whose details and actions should be displayed. final TimelineCardModel card; /// Callback invoked when the modal should close. final VoidCallback onClose; /// Callback invoked when the status circle should toggle completion. final VoidCallback? onStatusPressed; /// Callback invoked when Push to next is selected. final Future Function(TimelineCardModel card)? onPushToNext; /// Callback invoked when Push to tomorrow is selected. final Future Function(TimelineCardModel card)? onPushToTomorrow; /// Callback invoked when Push to backlog is selected. final Future Function(TimelineCardModel card)? onPushToBacklog; /// Callback invoked when Remove is selected. final Future Function(TimelineCardModel card)? onRemove; /// Callback invoked with drafted child tasks after Break up is confirmed. final Future Function( TimelineCardModel card, List children, )? onBreakUp; /// Opens the break-up dialog and forwards drafted child tasks to /// [onBreakUp] when the user confirms with at least one task. Future _handleBreakUp(BuildContext context) async { final drafts = await showDialog>( context: context, builder: (_) => const BreakUpDialog(), ); if (drafts == null || drafts.isEmpty) { return; } await onBreakUp!(card, drafts); } /// 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 accent = _accentFor(card.visualKind); return Material( color: Colors.transparent, child: Container( key: const ValueKey('task-selection-modal'), width: 548, padding: const EdgeInsets.fromLTRB(24, 22, 24, 20), decoration: BoxDecoration( color: FocusFlowTokens.elevatedPanel.withValues(alpha: 0.98), borderRadius: BorderRadius.circular(FocusFlowTokens.modalRadius), border: Border.all(color: accent.withValues(alpha: 0.86)), ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ _ModalStatusButton( card: card, color: accent, onPressed: onStatusPressed, ), const SizedBox(width: 28), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( card.title, style: const TextStyle( color: FocusFlowTokens.textPrimary, fontSize: FocusFlowTokens.modalTitleSize, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 6), _HeaderMetadata(card: card), ], ), ), const SizedBox(width: 14), _HeaderMetricIcons(card: card), const SizedBox(width: 14), IconButton( key: const ValueKey('close-task-modal'), onPressed: onClose, style: IconButton.styleFrom( backgroundColor: FocusFlowTokens.glassPanelStrong, foregroundColor: FocusFlowTokens.textPrimary, ), icon: const Icon(Icons.close), ), ], ), const SizedBox(height: 24), GridView.count( crossAxisCount: 2, crossAxisSpacing: 18, mainAxisSpacing: 12, childAspectRatio: 5.3, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: [ _PushActionButton( card: card, onPushToNext: onPushToNext, onPushToTomorrow: onPushToTomorrow, onPushToBacklog: onPushToBacklog, ), _ActionButton( icon: Icons.inventory_2_outlined, label: 'Backlog', onTap: onPushToBacklog == null ? null : () { unawaited(onPushToBacklog!(card)); }, ), _ActionButton( icon: Icons.apps, label: 'Break up', onTap: onBreakUp == null ? null : () { unawaited(_handleBreakUp(context)); }, ), _ActionButton( icon: Icons.delete_outline, label: 'Remove', onTap: onRemove == null ? null : () { unawaited(onRemove!(card)); }, ), ], ), const SizedBox(height: 14), const Text( 'Options apply after confirmation.', style: TextStyle(color: FocusFlowTokens.textMuted, fontSize: 14), ), ], ), ), ); } }