EssenceDailyCore/lib/pages/daily_focus_page.dart

436 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive_flutter/hive_flutter.dart';
import '../models/task_item.dart';
import '../services/database_service.dart';
import '../widgets/elegant_header.dart';
import '../widgets/task_tile.dart';
import 'focus_timer_page.dart';
class DailyFocusPage extends StatefulWidget {
const DailyFocusPage({super.key});
@override
State<DailyFocusPage> createState() => _DailyFocusPageState();
}
class _DailyFocusPageState extends State<DailyFocusPage> {
late Box<TaskItem> _tasksBox;
@override
void initState() {
super.initState();
_tasksBox = DatabaseService.getTasksBox();
}
List<TaskItem> _getSortedTasks() {
final tasks = _tasksBox.values.toList();
tasks.sort((a, b) => a.sortIndex.compareTo(b.sortIndex));
return tasks;
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: _tasksBox.listenable(),
builder: (context, Box<TaskItem> box, _) {
final allTasks = _getSortedTasks();
final essenceTasks = allTasks.take(3).toList();
final backlogTasks = allTasks.skip(3).toList();
final completedCount = allTasks.where((t) => t.isCompleted).length;
return Scaffold(
body: SafeArea(
bottom: false,
child: Column(
children: [
const ElegantHeader(),
Expanded(
child: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
// Header Section
SliverPadding(
padding: const EdgeInsets.fromLTRB(28, 10, 28, 30),
sliver: SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Good Morning.",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.grey.shade400,
letterSpacing: 1,
),
),
const SizedBox(height: 8),
const Text(
"Your Essence.",
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w900,
color: Colors.black,
height: 1.1,
letterSpacing: -1,
),
),
const SizedBox(height: 20),
// Progress Bar
Stack(
children: [
Container(
height: 4,
width: 100,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(2),
),
),
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: 4,
width: allTasks.isEmpty
? 0
: (100 * (completedCount / allTasks.length)),
decoration: BoxDecoration(
color: const Color(0xFFFF7F50),
borderRadius: BorderRadius.circular(2),
),
),
],
),
const SizedBox(height: 8),
Text(
"$completedCount / ${allTasks.length} Completed",
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
),
),
],
),
),
),
// Essence (Top 3)
if (essenceTasks.isNotEmpty)
SliverReorderableList(
itemCount: essenceTasks.length,
onReorder: (old, newIdx) =>
_onReorder(allTasks, old, newIdx),
itemBuilder: (context, index) {
final task = essenceTasks[index];
return ReorderableDragStartListener(
key: ValueKey(task.id),
index: index,
child: TaskTile(
task: task,
index: index,
isPriority: true,
isTopOne: index == 0,
onTap: () => _showTaskDetails(context, task),
onFocusTap: index == 0
? () => _startFocusMode(context, task)
: null,
onDismissed: () => _tasksBox.delete(task.id),
),
);
},
),
// Backlog Divider
if (backlogTasks.isNotEmpty)
SliverPadding(
padding: const EdgeInsets.fromLTRB(28, 40, 28, 15),
sliver: SliverToBoxAdapter(
child: Row(
children: [
Text(
"UP NEXT",
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w800,
color: Colors.grey.shade300,
letterSpacing: 1.2,
),
),
const SizedBox(width: 10),
Expanded(
child: Divider(color: Colors.grey.shade100),
),
],
),
),
),
// Backlog List
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final actualIndex = index + 3;
return TaskTile(
task: backlogTasks[index],
index: actualIndex,
isPriority: false,
onTap: () =>
_showTaskDetails(context, backlogTasks[index]),
onDismissed: () =>
_tasksBox.delete(backlogTasks[index].id),
);
},
childCount: backlogTasks.length,
),
),
const SliverToBoxAdapter(child: SizedBox(height: 120)),
],
),
),
],
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => _showAddTaskModal(context),
backgroundColor: Colors.black,
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
label: const Row(
children: [
Icon(Icons.add, color: Colors.white, size: 20),
SizedBox(width: 8),
Text(
"New Task",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
},
);
}
void _onReorder(List<TaskItem> allTasks, int oldIndex, int newIndex) async {
if (newIndex > oldIndex) newIndex -= 1;
final item = allTasks.removeAt(oldIndex);
allTasks.insert(newIndex, item);
for (int i = 0; i < allTasks.length; i++) {
final task = allTasks[i];
task.sortIndex = i;
await _tasksBox.put(task.id, task);
}
HapticFeedback.selectionClick();
}
void _showAddTaskModal(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Container(
padding: const EdgeInsets.all(28),
height: 220,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"What is essential?",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey.shade400,
),
),
const SizedBox(height: 12),
TextField(
autofocus: true,
decoration: InputDecoration(
hintText: "Enter task name...",
hintStyle: TextStyle(
color: Colors.grey.shade300,
fontSize: 22,
fontWeight: FontWeight.w500,
),
border: InputBorder.none,
),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.w600,
),
onSubmitted: (value) {
if (value.isNotEmpty) {
final newId = DateTime.now().millisecondsSinceEpoch.toString();
final newTask = TaskItem(
id: newId,
title: value,
createdAt: DateTime.now(),
sortIndex: _tasksBox.length,
);
_tasksBox.put(newId, newTask);
Navigator.pop(context);
}
},
),
],
),
),
),
);
}
void _showTaskDetails(BuildContext context, TaskItem task) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
useRootNavigator: true,
builder: (_) => DraggableScrollableSheet(
initialChildSize: 0.55,
minChildSize: 0.4,
maxChildSize: 0.9,
expand: false,
builder: (_, controller) => Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
child: ListView(
controller: controller,
children: [
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey.shade200,
borderRadius: BorderRadius.circular(2),
),
),
),
const SizedBox(height: 30),
TextFormField(
initialValue: task.title,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
decoration: const InputDecoration(border: InputBorder.none),
onChanged: (val) {
task.title = val;
_tasksBox.put(task.id, task);
},
),
const SizedBox(height: 20),
_buildEditableDetail(Icons.sort, "Note", task.notes ?? "", (val) {
task.notes = val;
_tasksBox.put(task.id, task);
}),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () {
task.isCompleted = !task.isCompleted;
_tasksBox.put(task.id, task);
Navigator.pop(context);
},
style: ElevatedButton.styleFrom(
backgroundColor: task.isCompleted
? Colors.grey.shade300
: Colors.black,
padding: const EdgeInsets.symmetric(vertical: 16),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: Text(
task.isCompleted ? "Mark as Incomplete" : "Complete Task",
style: TextStyle(
color: task.isCompleted ? Colors.black54 : Colors.white,
fontWeight: FontWeight.bold,
),
),
),
TextButton(
onPressed: () {
_tasksBox.delete(task.id);
Navigator.pop(context);
},
child: const Text(
"Delete",
style: TextStyle(color: Colors.redAccent),
),
)
],
),
),
),
);
}
Widget _buildEditableDetail(
IconData icon,
String label,
String value,
Function(String) onChange,
) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(16),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 20, color: Colors.grey.shade400),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.grey.shade400,
),
),
TextFormField(
initialValue: value,
maxLines: null,
decoration: const InputDecoration(
hintText: "Tap to add details...",
border: InputBorder.none,
isDense: true,
),
style: const TextStyle(fontSize: 15, height: 1.5),
onChanged: onChange,
),
],
),
),
],
),
);
}
void _startFocusMode(BuildContext context, TaskItem task) {
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
FocusTimerPage(task: task),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
),
);
}
}