focus-flow/apps/focus_flow_flutter/lib/widgets/task_selection_modal.dart

158 lines
5.5 KiB
Dart

// 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 '../models/today_screen_models.dart';
import '../theme/focus_flow_tokens.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,
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<void> Function(TimelineCardModel card)? onPushToNext;
/// Callback invoked when Push to tomorrow is selected.
final Future<void> Function(TimelineCardModel card)? onPushToTomorrow;
/// Callback invoked when Push to backlog is selected.
final Future<void> Function(TimelineCardModel card)? onPushToBacklog;
/// Callback invoked when Remove is selected.
final Future<void> Function(TimelineCardModel card)? onRemove;
/// 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',
),
const _ActionButton(icon: Icons.apps, label: 'Break up'),
_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),
),
],
),
),
);
}
}