Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
775046c
feat: add create task screen and fix home screen provider errors
Ender-Via Apr 9, 2026
c8c7e6d
Feature/user profile (#30)
tqha1011 Apr 9, 2026
35f0285
feat(priority task):Implement priority and tag selection in task crea…
anhkietbienhoa-crypto Apr 9, 2026
a2a1572
Remove comment about main UI in home_screen.dart
Ender-Via Apr 9, 2026
5b59d35
Update README.md (#32)
tqha1011 Apr 9, 2026
ae8e44c
feat: Priority selector and tag system verson2 (#33)
anhkietbienhoa-crypto Apr 10, 2026
7a77888
fix: error screen in main.dart (#34)
tqha1011 Apr 10, 2026
986a56d
Enhance authentication UI and implement Focus feature set (#35)
hoanghaoz Apr 12, 2026
c0755d9
build(deps)(deps): bump shared_preferences from 2.5.4 to 2.5.5 in /sr…
dependabot[bot] Apr 13, 2026
4eca043
Finish merging and fix data type error
Ender-Via Apr 15, 2026
c4b702b
Feature/user profile (#37)
tqha1011 Apr 17, 2026
9ff446c
fixed codes, added save delete and create tasks and notes
Ender-Via Apr 17, 2026
f6c381d
Can now change tasks, delete tasks, add task, can now add notes into …
Ender-Via Apr 18, 2026
a759ad7
Merge branch 'main' into create-task
tqha1011 Apr 18, 2026
5505d92
Delete .vs/TaskManagement.slnx/v18/.wsuo
tqha1011 Apr 18, 2026
e53c023
Delete .vs directory
tqha1011 Apr 18, 2026
23751e5
Delete .vscode directory
tqha1011 Apr 18, 2026
56f60f4
Delete src/.gitignore
tqha1011 Apr 18, 2026
4c82a73
Delete src/lib/features/auth/viewmodels/task_provider.dart
tqha1011 Apr 18, 2026
2ab131e
Delete src/web_entrypoint.dart
tqha1011 Apr 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
337 changes: 337 additions & 0 deletions src/lib/features/main/view/screens/create_task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,337 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import '../../../../core/theme/app_colors.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

class CreateTaskProvider extends ChangeNotifier {
final _supabase = Supabase.instance.client;

// Form state variables
String _taskName = "";
String _selectedCategory = "Development";
DateTime _selectedDate = DateTime.now();
TimeOfDay _startTime = TimeOfDay(hour: 10, minute: 0);
TimeOfDay _endTime = TimeOfDay(hour: 11, minute: 0);
String _description = "";
bool _isLoading = false;

// Getters for UI consumption
String get taskName => _taskName;
String get selectedCategory => _selectedCategory;
DateTime get selectedDate => _selectedDate;
TimeOfDay get startTime => _startTime;
TimeOfDay get endTime => _endTime;
String get description => _description;
bool get isLoading => _isLoading;

// State update methods
void setTaskName(String value) { _taskName = value; notifyListeners(); }
void setCategory(String value) { _selectedCategory = value; notifyListeners(); }
void setDate(DateTime value) { _selectedDate = value; notifyListeners(); }
void setStartTime(TimeOfDay value) { _startTime = value; notifyListeners(); }
void setEndTime(TimeOfDay value) { _endTime = value; notifyListeners(); }
void setDescription(String value) { _description = value; notifyListeners(); }

/// Validates input and persists the new task to Supabase
Future<void> onCreateTaskPressed(BuildContext context) async {

if (_taskName.trim().isEmpty) {
_showSnackBar(context, "Task name is required.");
return;
}

final user = _supabase.auth.currentUser;
if (user == null) {
_showSnackBar(context, "Session not found. Please re-authenticate.");
return;
}

_isLoading = true;
notifyListeners();

try {
// Map category labels to specific database IDs based on the provided schema
final categoryMapping = {
"Development": 1,
"Research": 2,
"Design": 3,
"Backend": 4,
};
int categoryId = categoryMapping[_selectedCategory] ?? 1;

// Construct a unified timestamp from selected date and start time
final scheduledDateTime = DateTime(
_selectedDate.year,
_selectedDate.month,
_selectedDate.day,
_startTime.hour,
_startTime.minute,
);

// Execute insert operation.
// Note: 'description' is omitted as it does not exist in the current 'task' table schema.
await _supabase.from('task').insert({
'title': _taskName.trim(),
'status': 0,
'priority': 1,
'profile_id': user.id,
'category_id': categoryId,
'create_at': scheduledDateTime.toIso8601String(),
});

if (context.mounted) {
_showSnackBar(context, "Task created successfully.");
Navigator.pop(context);
}
} catch (e) {
debugPrint("Data Persistence Error: $e");
if (context.mounted) {
_showSnackBar(context, "Database synchronization failed.");
}
} finally {
_isLoading = false;
notifyListeners();
}
}

void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}
}

class CreateTaskScreen extends StatelessWidget {
const CreateTaskScreen({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: SafeArea(
bottom: false,
child: Column(
children: [
_buildHeader(context),
Expanded(
child: Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(40)),
),
child: SingleChildScrollView(
padding: const EdgeInsets.all(30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle("Task Name"),
_buildInputField(
hint: "Enter task name...",
onChanged: (v) => context.read<CreateTaskProvider>().setTaskName(v),
),
const SizedBox(height: 25),
_buildSectionTitle("Select Category"),
_buildCategorySelector(),
const SizedBox(height: 25),
_buildDateSelector(context),
const SizedBox(height: 25),
_buildTimeSelectors(context),
const SizedBox(height: 25),
_buildSectionTitle("Description"),
_buildInputField(
hint: "Add supplementary notes...",
maxLines: 4,
onChanged: (v) => context.read<CreateTaskProvider>().setDescription(v),
),
const SizedBox(height: 40),
_buildCreateButton(context),
const SizedBox(height: 30),
],
),
),
),
),
],
),
),
);
}

Widget _buildHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 15),
child: Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.black, size: 20),
onPressed: () => Navigator.pop(context),
),
const Expanded(
child: Center(
child: Text(
"New Task",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.black),
),
),
),
const SizedBox(width: 48),
],
),
);
}

Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Text(
title,
style: const TextStyle(fontSize: 16, color: AppColors.primaryBlue, fontWeight: FontWeight.bold)
),
);
}

Widget _buildInputField({required String hint, int maxLines = 1, Function(String)? onChanged}) {
return TextField(
maxLines: maxLines,
onChanged: onChanged,
style: const TextStyle(fontSize: 18, color: Colors.black),
decoration: InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: AppColors.textDark.withOpacity(0.4), fontSize: 18),
enabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: Colors.black12)),
focusedBorder: const UnderlineInputBorder(borderSide: BorderSide(color: AppColors.primaryBlue)),
),
);
}

Widget _buildCategorySelector() {
return Consumer<CreateTaskProvider>(
builder: (context, provider, child) {
final categories = ["Development", "Research", "Design", "Backend"];
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: categories.map((cat) {
final isSelected = cat == provider.selectedCategory;
return GestureDetector(
onTap: () => provider.setCategory(cat),
child: Container(
margin: const EdgeInsets.only(right: 15),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
decoration: BoxDecoration(
color: isSelected ? AppColors.primaryBlue : AppColors.inputBackground,
borderRadius: BorderRadius.circular(10),
),
child: Text(
cat,
style: TextStyle(
color: isSelected ? Colors.white : AppColors.textLightBlue,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}).toList(),
),
);
},
);
}

Widget _buildDateSelector(BuildContext context) {
return Consumer<CreateTaskProvider>(
builder: (context, provider, child) {
final formattedDate = DateFormat('EEEE, d MMMM').format(provider.selectedDate);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle("Date"),
Row(
children: [
Expanded(
child: Text(formattedDate, style: const TextStyle(color: Colors.black, fontSize: 18)),
),
GestureDetector(
onTap: () async {
final picked = await showDatePicker(
context: context,
initialDate: provider.selectedDate,
firstDate: DateTime.now(),
lastDate: DateTime(2100),
);
if (picked != null) provider.setDate(picked);
},
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: AppColors.primaryBlue, borderRadius: BorderRadius.circular(12)),
child: const Icon(Icons.calendar_month, color: Colors.white, size: 20),
),
)
],
),
const Divider(color: Colors.black12),
],
);
},
);
}

Widget _buildTimeSelectors(BuildContext context) {
return Row(
children: [
Expanded(child: _timeField(context, "Start Time", true)),
const SizedBox(width: 30),
Expanded(child: _timeField(context, "End Time", false)),
],
);
}

Widget _timeField(BuildContext context, String title, bool isStart) {
return Consumer<CreateTaskProvider>(
builder: (context, provider, child) {
final time = isStart ? provider.startTime : provider.endTime;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle(title),
GestureDetector(
onTap: () async {
final picked = await showTimePicker(context: context, initialTime: time);
if (picked != null) {
isStart ? provider.setStartTime(picked) : provider.setEndTime(picked);
}
},
child: Row(
children: [
Text(time.format(context), style: const TextStyle(color: Colors.black, fontSize: 18)),
const Icon(Icons.keyboard_arrow_down, color: AppColors.primaryBlue, size: 18),
],
),
),
const Divider(color: Colors.black12),
],
);
},
);
}

Widget _buildCreateButton(BuildContext context) {
final provider = context.watch<CreateTaskProvider>();
return Center(
child: SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
onPressed: provider.isLoading ? null : () => provider.onCreateTaskPressed(context),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primaryBlue,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
child: provider.isLoading
? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(color: Colors.white, strokeWidth: 2))
: const Text("Confirm Task Creation", style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
),
),
);
}
}
20 changes: 20 additions & 0 deletions src/lib/features/tasks/model/task_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,23 @@ class TaskModel {
this.tags = const [],
});
}

class NoteModel {
final int id;
final String content;
final bool pinned;

NoteModel({
required this.id,
required this.content,
this.pinned = false,
});

factory NoteModel.fromJson(Map<String, dynamic> json) {
return NoteModel(
id: json['id'],
content: json['content'] ?? '',
pinned: json['pinned'] ?? false,
);
}
}
Loading
Loading