Files
focusflow/lib/features/tasks/presentation/screens/task_detail_screen.dart
Oracle Public Cloud User 50931d839d Initial scaffold: FocusFlow ADHD Task Manager Flutter app
BLoC/Cubit state management, ADHD-friendly theme (calming teal, no red),
GetIt DI, GoRouter navigation. Screens: task dashboard, focus mode,
task create/detail, streaks, time perception, settings, onboarding, auth.
Custom widgets: TaskCard, RewardPopup, StreakRing, GentleNudgeCard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 15:53:58 +00:00

221 lines
7.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:focusflow_shared/focusflow_shared.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/time_visualizer.dart';
import '../widgets/energy_selector.dart';
/// Task detail / edit screen.
///
/// Shows title, description, energy level selector, time estimate,
/// tags, category. Provides save and delete actions.
class TaskDetailScreen extends StatefulWidget {
final String taskId;
const TaskDetailScreen({super.key, required this.taskId});
@override
State<TaskDetailScreen> createState() => _TaskDetailScreenState();
}
class _TaskDetailScreenState extends State<TaskDetailScreen> {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
final _minutesController = TextEditingController();
final _tagsController = TextEditingController();
final _categoryController = TextEditingController();
String _energyLevel = 'medium';
// Placeholder task — in production, load from BLoC.
Task? _task;
bool _loading = true;
@override
void initState() {
super.initState();
_loadTask();
}
Future<void> _loadTask() async {
// Simulated placeholder — in production the BLoC fetches from the API.
await Future<void>.delayed(const Duration(milliseconds: 300));
final task = Task(
id: widget.taskId,
userId: 'local',
title: 'Sample Task',
description: 'Tap to edit this task.',
energyLevel: 'medium',
estimatedMinutes: 25,
actualMinutes: 20,
tags: const ['work', 'focus'],
category: 'work',
createdAt: DateTime.now(),
);
if (!mounted) return;
setState(() {
_task = task;
_loading = false;
_titleController.text = task.title;
_descriptionController.text = task.description ?? '';
_minutesController.text = task.estimatedMinutes?.toString() ?? '';
_tagsController.text = task.tags.join(', ');
_categoryController.text = task.category ?? '';
_energyLevel = task.energyLevel;
});
}
@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
_minutesController.dispose();
_tagsController.dispose();
_categoryController.dispose();
super.dispose();
}
void _save() {
if (_formKey.currentState?.validate() ?? false) {
// TODO: dispatch update via BLoC.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Task saved.')),
);
context.pop();
}
}
void _delete() {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Delete task?'),
content: const Text('This cannot be undone.'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.pop(ctx);
// TODO: dispatch delete via BLoC.
context.pop();
},
child: const Text('Delete', style: TextStyle(color: AppColors.error)),
),
],
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
if (_loading) {
return Scaffold(
appBar: AppBar(title: const Text('Task')),
body: const Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Task Detail'),
actions: [
IconButton(
icon: const Icon(Icons.delete_outline_rounded, color: AppColors.error),
onPressed: _delete,
tooltip: 'Delete',
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Title ──────────────────────────────────────────
TextFormField(
controller: _titleController,
decoration: const InputDecoration(labelText: 'Title'),
style: theme.textTheme.titleLarge,
validator: (v) =>
(v == null || v.trim().isEmpty) ? 'Give it a name' : null,
),
const SizedBox(height: 16),
// ── Description ────────────────────────────────────
TextFormField(
controller: _descriptionController,
decoration: const InputDecoration(labelText: 'Description (optional)'),
maxLines: 3,
),
const SizedBox(height: 20),
// ── Energy level ───────────────────────────────────
Text('Energy level', style: theme.textTheme.titleSmall),
const SizedBox(height: 8),
EnergySelector(
value: _energyLevel,
onChanged: (v) => setState(() => _energyLevel = v),
),
const SizedBox(height: 20),
// ── Time estimate ──────────────────────────────────
TextFormField(
controller: _minutesController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Estimated minutes',
suffixText: 'min',
),
),
const SizedBox(height: 20),
// ── Time visualizer (if there is actual data) ──────
if (_task != null &&
_task!.estimatedMinutes != null &&
_task!.actualMinutes != null)
TimeVisualizer(
estimatedMinutes: _task!.estimatedMinutes!,
actualMinutes: _task!.actualMinutes,
),
const SizedBox(height: 20),
// ── Tags ───────────────────────────────────────────
TextFormField(
controller: _tagsController,
decoration: const InputDecoration(
labelText: 'Tags (comma separated)',
prefixIcon: Icon(Icons.label_outline),
),
),
const SizedBox(height: 16),
// ── Category ───────────────────────────────────────
TextFormField(
controller: _categoryController,
decoration: const InputDecoration(
labelText: 'Category',
prefixIcon: Icon(Icons.category_outlined),
),
),
const SizedBox(height: 32),
// ── Save button ────────────────────────────────────
FilledButton(
onPressed: _save,
child: const Text('Save'),
),
],
),
),
),
);
}
}