Skip to content

Commit 388c27c

Browse files
authored
Setup (#10)
* chore: configure .env * setup: core color theme * setup: supabase init
1 parent 18fb0f2 commit 388c27c

8 files changed

Lines changed: 836 additions & 1 deletion

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:task_management_app/features/tasks/view/screens/home_screen.dart';
3+
import '../../../../core/theme/app_colors.dart';
4+
import '../../../statistics/view/screens/statistics_screen.dart';
5+
// import '../../../tasks/view/screens/home_screen.dart'; // Màn hình Home bạn đã làm
6+
7+
class MainScreen extends StatefulWidget {
8+
const MainScreen({super.key});
9+
10+
@override
11+
State<MainScreen> createState() => _MainScreenState();
12+
}
13+
14+
class _MainScreenState extends State<MainScreen> {
15+
int _currentIndex = 0; // Mặc định mở tab công việc
16+
17+
// Danh sách các màn hình tương ứng với các tab
18+
final List<Widget> _screens = [
19+
const Center(child: const HomeScreen()),
20+
const Center(child: Text('Màn hình Lịch')),
21+
const Center(child: Text('Màn hình Tập trung')),
22+
const StatisticsScreen(), // Màn hình Thống kê vừa tạo
23+
const Center(child: Text('Màn hình Cài đặt')),
24+
];
25+
26+
@override
27+
Widget build(BuildContext context) {
28+
return Scaffold(
29+
extendBody: true, // Cho phép body chui xuống dưới bottom bar (để làm bar nổi/trong suốt nếu muốn)
30+
body: IndexedStack(
31+
index: _currentIndex,
32+
children: _screens, // IndexedStack giúp giữ state của các tab không bị reset khi chuyển qua lại
33+
),
34+
bottomNavigationBar: Container(
35+
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
36+
decoration: BoxDecoration(
37+
color: Colors.white,
38+
borderRadius: const BorderRadius.only(topLeft: Radius.circular(30), topRight: Radius.circular(30)),
39+
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 20, offset: const Offset(0, -5))],
40+
),
41+
child: SafeArea(
42+
child: Row(
43+
mainAxisAlignment: MainAxisAlignment.spaceAround,
44+
children: [
45+
_buildNavItem(Icons.checklist_rounded, 'Công việc', 0),
46+
_buildNavItem(Icons.calendar_today_rounded, 'Lịch', 1),
47+
_buildNavItem(Icons.timer_rounded, 'Tập trung', 2),
48+
_buildNavItem(Icons.bar_chart_rounded, 'Thống kê', 3),
49+
_buildNavItem(Icons.settings_rounded, 'Cài đặt', 4),
50+
],
51+
),
52+
),
53+
),
54+
);
55+
}
56+
57+
// Hàm tạo từng item trên bottom bar với hiệu ứng AnimatedContainer
58+
Widget _buildNavItem(IconData icon, String label, int index) {
59+
bool isSelected = _currentIndex == index;
60+
61+
return GestureDetector(
62+
onTap: () => setState(() => _currentIndex = index),
63+
child: AnimatedContainer(
64+
duration: const Duration(milliseconds: 300),
65+
curve: Curves.easeInOut,
66+
padding: EdgeInsets.symmetric(horizontal: isSelected ? 15 : 10, vertical: 10),
67+
decoration: BoxDecoration(
68+
color: isSelected ? const Color(0xFFE8F0FE) : Colors.transparent, // Nền xanh nhạt khi được chọn
69+
borderRadius: BorderRadius.circular(15),
70+
),
71+
child: Column(
72+
mainAxisSize: MainAxisSize.min,
73+
children: [
74+
Icon(icon, color: isSelected ? AppColors.primaryBlue : AppColors.grayText, size: 24),
75+
const SizedBox(height: 4),
76+
Text(
77+
label,
78+
style: TextStyle(
79+
fontSize: 10,
80+
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
81+
color: isSelected ? AppColors.primaryBlue : AppColors.grayText,
82+
),
83+
)
84+
],
85+
),
86+
),
87+
);
88+
}
89+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import 'package:flutter/material.dart';
2+
import '../../../../core/theme/app_colors.dart';
3+
import '../../../tasks/model/task_model.dart';
4+
import '../widgets/statistics_widgets.dart';
5+
6+
class StatisticsScreen extends StatefulWidget {
7+
const StatisticsScreen({super.key});
8+
9+
@override
10+
State<StatisticsScreen> createState() => _StatisticsScreenState();
11+
}
12+
13+
class _StatisticsScreenState extends State<StatisticsScreen> {
14+
// Biến lưu trữ ngày đang được chọn trên biểu đồ (0 = T2, 1 = T3, 2 = T4...)
15+
int _selectedDayIndex = 2; // Mặc định chọn T4 (Index 2) giống trong thiết kế
16+
17+
// Dữ liệu giả lập phân loại theo ngày (0 đến 6)
18+
late Map<int, List<TaskModel>> _tasksByDay;
19+
20+
@override
21+
void initState() {
22+
super.initState();
23+
// Tạo mock data cho một vài ngày để test
24+
_tasksByDay = {
25+
0: [ // Thứ 2
26+
TaskModel(id: 'stat_t2_1', title: 'Họp team đầu tuần', description: 'Lên kế hoạch Sprint mới.', category: 'Development', startTime: const TimeOfDay(hour: 9, minute: 0), endTime: const TimeOfDay(hour: 10, minute: 0), date: DateTime.now()),
27+
],
28+
1: [ // Thứ 3
29+
TaskModel(id: 'stat_t3_1', title: 'Fix bug UI', description: 'Sửa lỗi hiển thị trên iOS.', category: 'Development', startTime: const TimeOfDay(hour: 14, minute: 0), endTime: const TimeOfDay(hour: 16, minute: 0), date: DateTime.now()),
30+
TaskModel(id: 'stat_t3_2', title: 'Đọc tài liệu Flutter', description: 'Nghiên cứu State Management.', category: 'Research', startTime: const TimeOfDay(hour: 20, minute: 0), endTime: const TimeOfDay(hour: 21, minute: 0), date: DateTime.now()),
31+
],
32+
2: [ // Thứ 4 (Mặc định)
33+
TaskModel(id: 'stat_t4_1', title: 'Thiết kế UI màn hình Dashboard', description: 'Hoàn thành bản thiết kế Figma.', category: 'Design', startTime: const TimeOfDay(hour: 10, minute: 30), endTime: const TimeOfDay(hour: 11, minute: 30), date: DateTime.now()),
34+
TaskModel(id: 'stat_t4_2', title: 'Học tiếng Anh - 30 phút', description: 'Ôn tập 50 từ vựng chuyên ngành IT qua Anki.', category: 'Research', startTime: const TimeOfDay(hour: 8, minute: 15), endTime: const TimeOfDay(hour: 8, minute: 45), date: DateTime.now()),
35+
TaskModel(id: 'stat_t4_3', title: 'Gửi báo cáo tuần cho sếp', description: 'Tổng hợp tiến độ dự án.', category: 'Development', startTime: const TimeOfDay(hour: 7, minute: 45), endTime: const TimeOfDay(hour: 8, minute: 0), date: DateTime.now()),
36+
],
37+
};
38+
}
39+
40+
// Hàm hỗ trợ chọn Icon dựa theo Category để UI sinh động hơn
41+
Icon _getIconForCategory(String category) {
42+
switch (category) {
43+
case 'Design': return const Icon(Icons.checklist_rtl_rounded, color: AppColors.primaryBlue);
44+
case 'Research': return const Icon(Icons.timer_outlined, color: Color(0xFFE67E22));
45+
case 'Development': return const Icon(Icons.calendar_month_outlined, color: Color(0xFF9B59B6));
46+
default: return const Icon(Icons.task_alt_rounded, color: Colors.green);
47+
}
48+
}
49+
50+
@override
51+
Widget build(BuildContext context) {
52+
// Lấy danh sách task của ngày đang chọn (nếu không có thì trả về list rỗng)
53+
List<TaskModel> currentTasks = _tasksByDay[_selectedDayIndex] ?? [];
54+
55+
return Scaffold(
56+
backgroundColor: const Color(0xFFF4F6F9),
57+
body: SafeArea(
58+
child: SingleChildScrollView(
59+
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
60+
child: Column(
61+
crossAxisAlignment: CrossAxisAlignment.start,
62+
children: [
63+
// Header
64+
Row(
65+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
66+
children: [
67+
const Row(
68+
children: [
69+
CircleAvatar(radius: 22, backgroundImage: NetworkImage('https://i.pravatar.cc/150?u=a042581f4e29026704d')),
70+
SizedBox(width: 15),
71+
Text('Thống kê', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))),
72+
],
73+
),
74+
Container(
75+
padding: const EdgeInsets.all(10),
76+
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
77+
child: const Icon(Icons.notifications_none_rounded, color: AppColors.primaryBlue),
78+
)
79+
],
80+
),
81+
const SizedBox(height: 30),
82+
83+
const DailyProgressCard(),
84+
const SizedBox(height: 25),
85+
86+
// Truyền State vào WeeklyChartCard
87+
WeeklyChartCard(
88+
selectedIndex: _selectedDayIndex,
89+
onDaySelected: (index) {
90+
setState(() {
91+
_selectedDayIndex = index; // Cập nhật lại UI khi chọn ngày khác
92+
});
93+
},
94+
),
95+
const SizedBox(height: 30),
96+
97+
Row(
98+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
99+
children: [
100+
const Text('Hoàn thành gần đây', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))),
101+
Text('Xem tất cả', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primaryBlue)),
102+
],
103+
),
104+
const SizedBox(height: 15),
105+
106+
// Render danh sách Task kèm hiệu ứng đổi mới
107+
AnimatedSwitcher(
108+
duration: const Duration(milliseconds: 300),
109+
child: currentTasks.isEmpty
110+
? Container(
111+
key: ValueKey('empty_$_selectedDayIndex'),
112+
padding: const EdgeInsets.all(30),
113+
alignment: Alignment.center,
114+
child: Text('Không có công việc nào hoàn thành vào ngày này.',
115+
style: TextStyle(color: Colors.grey.shade500), textAlign: TextAlign.center),
116+
)
117+
: Column(
118+
key: ValueKey('list_$_selectedDayIndex'),
119+
children: currentTasks.map((task) {
120+
return CompletedTaskCard(
121+
task: task,
122+
icon: _getIconForCategory(task.category),
123+
);
124+
}).toList(),
125+
),
126+
),
127+
128+
const SizedBox(height: 80),
129+
],
130+
),
131+
),
132+
),
133+
);
134+
}
135+
}

0 commit comments

Comments
 (0)