Skip to content

Commit 35f0285

Browse files
feat(priority task):Implement priority and tag selection in task creation version 1 (#31)
* feat(task): implement priority and tag selection features in task creation * Update README.md * Update README.md * Update README.md --------- Co-authored-by: Tran Quang Ha <[email protected]>
1 parent c8c7e6d commit 35f0285

8 files changed

Lines changed: 711 additions & 108 deletions

File tree

README.md

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
# Task Management App - Life Organizer
1+
## 📁 Chi tiết các file thay đổi
22

3-
## 1. Introduction
4-
This repository contains the source code for the **Task Management App (Life Organizer)**, developed as a project for the **SE346** course at the University of Information Technology - VNUHCM (UIT).
3+
* **`task_model.dart`**: Thêm thuộc tính `Priority` và tích hợp `TagModel`.
4+
* **`task_viewmodel.dart`**: Cập nhật logic xử lý trạng thái cho Provider.
5+
* **`priority_selector.dart`**: Thêm UI Widget hỗ trợ chọn mức độ ưu tiên.
6+
* **`tag_selector.dart`**: Thêm UI Widget hỗ trợ chọn các loại tag.
7+
* **`create_task_screen.dart`**: Tích hợp 2 widget chọn Priority và Tag vào form tạo nhiệm vụ.
8+
* **`main.dart`**: Đăng ký Provider mới cho hệ thống.
9+
* **`home_screen.dart`**: Cập nhật giao diện, hiển thị danh sách task được nhóm theo mức độ ưu tiên.
510

6-
Going beyond a traditional to-do list, this application is designed to be a comprehensive personal assistant. It helps users easily organize various life contexts, such as tracking utility bills, managing grocery shopping lists, building daily habits, and handling household chores efficiently.
7-
8-
## 2. Authors
9-
This project is built and maintained by a dedicated team of 4 members:
10-
* [Trần Quang Hạ](https://github.com/tqha1011)
11-
* [Nguyễn Lê Hoàng Hảo](https://github.com/hoanghaoz)
12-
* [Nguyễn Anh Kiệt](https://github.com/anhkietbienhoa-crypto)
13-
* [Nguyễn Trí Kiệt](https://github.com/Ender-Via)
14-
15-
## 3. Tech Stack
16-
The application is built with a focus on performance, security, and clean code principles, strictly following the **Feature-Based MVVM** architecture:
17-
* **Frontend:** Flutter
18-
* **Backend & Database:** Supabase (PostgreSQL, Auth, Row Level Security)
19-
* **Code Quality & CI/CD:** GitHub Actions, SonarCloud
20-
21-
## 4. Documentation
22-
For a deeper dive into our system design and development workflows, please explore the attached documentation:
23-
* [Flutter App Architecture](documentation/architecture/flutter-architecture.md)
24-
* [Database Schema & ERD](documentation/architecture/database-schema.md)
25-
* [Git & Conventional Commits Guidelines](documentation/guidelines/conventional-commit.md)
11+
<img width="464" height="195" alt="image" src="https://github.com/user-attachments/assets/7a2244b4-82e4-4354-8029-1559c6c7766e" />
Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,69 @@
11
import 'package:flutter/material.dart';
22

3+
// ─── Priority Enum ───────────────────────────────────────────
4+
enum Priority { low, medium, high, urgent }
5+
6+
extension PriorityExtension on Priority {
7+
String get label {
8+
switch (this) {
9+
case Priority.low:
10+
return 'Low';
11+
case Priority.medium:
12+
return 'Medium';
13+
case Priority.high:
14+
return 'High';
15+
case Priority.urgent:
16+
return 'Urgent';
17+
}
18+
}
19+
20+
Color get color {
21+
switch (this) {
22+
case Priority.low:
23+
return const Color(0xFF4CAF50); // xanh lá
24+
case Priority.medium:
25+
return const Color(0xFF2196F3); // xanh dương
26+
case Priority.high:
27+
return const Color(0xFFFF9800); // cam
28+
case Priority.urgent:
29+
return const Color(0xFFF44336); // đỏ
30+
}
31+
}
32+
33+
IconData get icon {
34+
switch (this) {
35+
case Priority.low:
36+
return Icons.flag_outlined;
37+
case Priority.medium:
38+
return Icons.flag;
39+
case Priority.high:
40+
return Icons.flag;
41+
case Priority.urgent:
42+
return Icons.flag;
43+
}
44+
}
45+
}
46+
47+
// ─── Tag Model ───────────────────────────────────────────────
48+
class TagModel {
49+
final String id;
50+
final String name;
51+
final Color color;
52+
53+
const TagModel({required this.id, required this.name, required this.color});
54+
}
55+
56+
// ─── Task Model ──────────────────────────────────────────────
357
class TaskModel {
4-
final String id; // ID duy nhất để làm Hero tag và gọi API sau này
58+
final String id;
559
String title;
660
String description;
761
String category;
862
TimeOfDay startTime;
963
TimeOfDay endTime;
1064
DateTime date;
65+
Priority priority;
66+
List<TagModel> tags;
1167

1268
TaskModel({
1369
required this.id,
@@ -17,5 +73,7 @@ class TaskModel {
1773
required this.startTime,
1874
required this.endTime,
1975
required this.date,
76+
this.priority = Priority.medium,
77+
this.tags = const [],
2078
});
21-
}
79+
}

src/lib/features/tasks/view/screens/create_task_screen.dart

Lines changed: 133 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import 'package:flutter/material.dart';
22
import 'package:intl/intl.dart';
3+
import 'package:provider/provider.dart';
4+
import '../../../../core/theme/app_colors.dart';
35
import '../../../../core/widgets/custom_input_field.dart';
6+
import '../../model/task_model.dart';
7+
import '../../viewmodel/task_viewmodel.dart';
48
import '../widgets/task_widgets.dart';
9+
import '../widgets/priority_selector.dart';
10+
import '../widgets/tag_selector.dart';
511

612
class CreateTaskScreen extends StatefulWidget {
713
const CreateTaskScreen({super.key});
@@ -11,8 +17,12 @@ class CreateTaskScreen extends StatefulWidget {
1117
}
1218

1319
class _CreateTaskScreenState extends State<CreateTaskScreen> {
14-
final TextEditingController _nameController = TextEditingController(text: 'Team Meeting');
15-
final TextEditingController _descController = TextEditingController(text: 'Discuss all questions about new projects');
20+
final TextEditingController _nameController = TextEditingController(
21+
text: 'Team Meeting',
22+
);
23+
final TextEditingController _descController = TextEditingController(
24+
text: 'Discuss all questions about new projects',
25+
);
1626
DateTime _selectedDate = DateTime.now();
1727
TimeOfDay _startTime = const TimeOfDay(hour: 10, minute: 0);
1828
TimeOfDay _endTime = const TimeOfDay(hour: 11, minute: 0);
@@ -28,15 +38,20 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
2838
body: SafeArea(
2939
child: Column(
3040
children: [
41+
// ─── Header ───────────────────────────────────────
3142
Container(
3243
decoration: BoxDecoration(
3344
color: Theme.of(context).colorScheme.surface,
3445
borderRadius: const BorderRadius.only(
35-
bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30)),
46+
bottomLeft: Radius.circular(30),
47+
bottomRight: Radius.circular(30),
48+
),
3649
boxShadow: [
3750
BoxShadow(
38-
color: Colors.black.withValues(alpha: 0.05),
39-
blurRadius: 10, offset: const Offset(0, 5))
51+
color: Colors.black.withValues(alpha: 0.05),
52+
blurRadius: 10,
53+
offset: const Offset(0, 5),
54+
),
4055
],
4156
),
4257
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
@@ -61,25 +76,46 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
6176
],
6277
),
6378
),
79+
80+
// ─── Body ─────────────────────────────────────────
6481
Expanded(
6582
child: SingleChildScrollView(
6683
padding: const EdgeInsets.all(25.0),
6784
child: Column(
6885
crossAxisAlignment: CrossAxisAlignment.start,
6986
children: [
70-
Text('Create New Task', style: Theme.of(context).textTheme.headlineMedium),
87+
Text(
88+
'Create New Task',
89+
style: Theme.of(context).textTheme.headlineMedium,
90+
),
7191
const SizedBox(height: 25),
72-
CustomInputField(label: 'Task Name', hint: 'Enter task name', controller: _nameController),
92+
93+
// Task Name
94+
CustomInputField(
95+
label: 'Task Name',
96+
hint: 'Enter task name',
97+
controller: _nameController,
98+
),
7399
const SizedBox(height: 20),
74-
Text('Select Category', style: Theme.of(context).textTheme.labelLarge),
100+
101+
// Category
102+
Text(
103+
'Select Category',
104+
style: Theme.of(context).textTheme.labelLarge,
105+
),
75106
const SizedBox(height: 10),
76107
SizedBox(
77108
height: 40,
78109
child: ListView.builder(
79110
scrollDirection: Axis.horizontal,
80111
itemCount: 4,
81112
itemBuilder: (context, index) {
82-
List<String> categories = ['Development', 'Research', 'Design', 'Backend'];
113+
List<String> categories = [
114+
'Development',
115+
'Research',
116+
'Design',
117+
'Backend',
118+
];
83119
bool isSelected = index == _selectedCategoryIndex;
84120
return Padding(
85121
padding: const EdgeInsets.only(right: 10),
@@ -112,20 +148,38 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
112148
),
113149
),
114150
const SizedBox(height: 20),
151+
152+
// ─── PRIORITY SELECTOR (MỚI) ──────────────
153+
const PrioritySelector(),
154+
const SizedBox(height: 20),
155+
156+
// ─── TAG SELECTOR (MỚI) ───────────────────
157+
const TagSelector(),
158+
const SizedBox(height: 20),
159+
160+
// Date
115161
Row(
116162
mainAxisAlignment: MainAxisAlignment.spaceBetween,
117163
children: [
118164
Column(
119165
crossAxisAlignment: CrossAxisAlignment.start,
120166
children: [
121-
Text('Date', style: Theme.of(context).textTheme.labelLarge),
167+
Text(
168+
'Date',
169+
style: Theme.of(context).textTheme.labelLarge,
170+
),
122171
const SizedBox(height: 5),
123172
InkWell(
124173
onTap: () async {
125174
final DateTime? picked = await showDatePicker(
126-
context: context, initialDate: _selectedDate,
127-
firstDate: DateTime(2000), lastDate: DateTime(2100));
128-
if (picked != null) setState(() => _selectedDate = picked);
175+
context: context,
176+
initialDate: _selectedDate,
177+
firstDate: DateTime(2000),
178+
lastDate: DateTime(2100),
179+
);
180+
if (picked != null) {
181+
setState(() => _selectedDate = picked);
182+
}
129183
},
130184
child: Column(
131185
crossAxisAlignment: CrossAxisAlignment.start,
@@ -146,7 +200,7 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
146200
)
147201
],
148202
),
149-
)
203+
),
150204
],
151205
),
152206
Container(
@@ -160,15 +214,24 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
160214
],
161215
),
162216
const SizedBox(height: 25),
217+
218+
// Time
163219
Row(
164220
children: [
165221
Expanded(
166222
child: Column(
167223
crossAxisAlignment: CrossAxisAlignment.start,
168224
children: [
169-
Text('Start time', style: Theme.of(context).textTheme.labelLarge),
225+
Text(
226+
'Start time',
227+
style: Theme.of(context).textTheme.labelLarge,
228+
),
170229
const SizedBox(height: 5),
171-
TimePickerWidget(time: _startTime, onChanged: (newTime) => setState(() => _startTime = newTime)),
230+
TimePickerWidget(
231+
time: _startTime,
232+
onChanged: (t) =>
233+
setState(() => _startTime = t),
234+
),
172235
],
173236
),
174237
),
@@ -177,27 +240,73 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
177240
child: Column(
178241
crossAxisAlignment: CrossAxisAlignment.start,
179242
children: [
180-
Text('End time', style: Theme.of(context).textTheme.labelLarge),
243+
Text(
244+
'End time',
245+
style: Theme.of(context).textTheme.labelLarge,
246+
),
181247
const SizedBox(height: 5),
182-
TimePickerWidget(time: _endTime, onChanged: (newTime) => setState(() => _endTime = newTime)),
248+
TimePickerWidget(
249+
time: _endTime,
250+
onChanged: (t) => setState(() => _endTime = t),
251+
),
183252
],
184253
),
185254
),
186255
],
187256
),
188257
const SizedBox(height: 25),
189-
CustomInputField(label: 'Description', hint: 'Enter task description', controller: _descController, maxLines: 2),
258+
259+
// Description
260+
CustomInputField(
261+
label: 'Description',
262+
hint: 'Enter task description',
263+
controller: _descController,
264+
maxLines: 2,
265+
),
190266
const SizedBox(height: 40),
267+
268+
// ─── Create Button ────────────────────────
191269
Center(
192270
child: ElevatedButton(
193-
onPressed: () {},
271+
onPressed: () {
272+
final viewModel = context.read<TaskViewModel>();
273+
final List<String> categories = [
274+
'Development',
275+
'Research',
276+
'Design',
277+
'Backend',
278+
];
279+
final newTask = TaskModel(
280+
id: DateTime.now().millisecondsSinceEpoch
281+
.toString(),
282+
title: _nameController.text,
283+
description: _descController.text,
284+
category: categories[_selectedCategoryIndex],
285+
startTime: _startTime,
286+
endTime: _endTime,
287+
date: _selectedDate,
288+
priority: viewModel.selectedPriority,
289+
tags: List.from(viewModel.selectedTags),
290+
);
291+
viewModel.addTask(newTask);
292+
viewModel.reset();
293+
Navigator.pop(context);
294+
},
194295
style: ElevatedButton.styleFrom(
195296
backgroundColor: Theme.of(context).colorScheme.primary,
196297
foregroundColor: Colors.white,
197-
padding: const EdgeInsets.symmetric(horizontal: 100, vertical: 15),
198-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
298+
padding: const EdgeInsets.symmetric(
299+
horizontal: 100,
300+
vertical: 15,
301+
),
302+
shape: RoundedRectangleBorder(
303+
borderRadius: BorderRadius.circular(15),
304+
),
305+
),
306+
child: const Text(
307+
'Create Task',
308+
style: TextStyle(fontSize: 18),
199309
),
200-
child: const Text('Create Task', style: TextStyle(fontSize: 18)),
201310
),
202311
),
203312
],
@@ -209,4 +318,4 @@ class _CreateTaskScreenState extends State<CreateTaskScreen> {
209318
),
210319
);
211320
}
212-
}
321+
}

0 commit comments

Comments
 (0)