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>
221 lines
7.6 KiB
Dart
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'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|