Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subtask#90 #160

Merged
merged 8 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 52 additions & 0 deletions lib/models/subtask.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'dart:convert';

class Subtask {
String title;
bool isCompleted;
Subtask({
required this.title,
required this.isCompleted,
});

Subtask copyWith({
String? title,
bool? isCompleted,
}) {
return Subtask(
title: title ?? this.title,
isCompleted: isCompleted ?? this.isCompleted,
);
}

Map<String, dynamic> toMap() {
return {
'title': title,
'isCompleted': isCompleted,
};
}

factory Subtask.fromMap(Map<String, dynamic> map) {
return Subtask(
title: map['title'] ?? '',
isCompleted: map['isCompleted'] ?? false,
);
}

String toJson() => json.encode(toMap());

factory Subtask.fromJson(String source) =>
Subtask.fromMap(json.decode(source));

@override
String toString() => 'Subtask(title: $title, isCompleted: $isCompleted)';

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is Subtask && other.title == title;
}

@override
int get hashCode => title.hashCode;
}
43 changes: 30 additions & 13 deletions lib/models/task.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';

import 'package:taskly/models/subtask.dart';

class Task {
String id;
String title;
Expand All @@ -9,21 +11,24 @@ class Task {
Color color;
int? recurringDays;
Task? dependency;
List<Subtask> subtasks;

bool get isRecurring => recurringDays != null;

bool get hasDeadline => deadline != null;

Task(
{required this.title,
this.description = '',
this.isCompleted = false,
this.deadline,
this.color = Colors.blue,
this.recurringDays,
this.dependency,
String? id})
: id = id ?? DateTime.now().microsecondsSinceEpoch.toString();
Task({
required this.title,
this.description = '',
this.isCompleted = false,
this.deadline,
this.color = Colors.blue,
this.recurringDays,
this.dependency,
String? id,
List<Subtask>? subtasks,
}) : id = id ?? DateTime.now().microsecondsSinceEpoch.toString(),
subtasks = subtasks ?? [];

// Convert a Task object to JSON
Map<String, dynamic> toJson() {
Expand All @@ -36,28 +41,40 @@ class Task {
'recurringDays': recurringDays,
'color': color.value,
'id': id,
'subtasks': subtasks.map((e) => e.toMap()).toList(),
};
}

// Create a Task object from JSON
factory Task.fromJson(Map<String, dynamic> json) {
List<Subtask> subtasks;
if (json['subtasks'] == null) {
subtasks = [];
} else {
subtasks =
(json['subtasks'] as List).map((e) => Subtask.fromMap(e)).toList();
}

return Task(
title: json['title'],
description: json['description'],
isCompleted: json['isCompleted'],
deadline:
json['deadline'] != null ? DateTime.parse(json['deadline']) : null,
deadline: DateTime.tryParse(json['deadline'] ?? ""),
dependency:
json['dependency'] != null ? Task.fromJson(json['dependency']) : null,
recurringDays: json['recurringDays'],
color: Color(json['color']),
id: json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
subtasks: subtasks,
);
}

void toggleCompletion() {
if (!isRecurring) {
isCompleted = !isCompleted;
for (var element in subtasks) {
element.isCompleted = true;
}
return;
}

Expand All @@ -69,6 +86,6 @@ class Task {

@override
String toString() {
return 'Task(id: $id, title: $title, description: $description, isCompleted: $isCompleted, deadline: $deadline, hasDeadline: $hasDeadline)';
return 'Task(id: $id, title: $title, description: $description, isCompleted: $isCompleted, deadline: $deadline, color: $color, recurringDays: $recurringDays, subtasks: $subtasks)';
}
}
7 changes: 7 additions & 0 deletions lib/screens/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,12 @@ void kudosForMeditation(int scoreChange, String mssg) async{
}
}

void _onSubtaskChanged(int index, Task t) async {
tasks[index] = t;
setState(() {});
await TaskStorage.saveTasks(tasks);
}

@override
Widget build(BuildContext context) {
return Scaffold(
Expand Down Expand Up @@ -382,6 +388,7 @@ void kudosForMeditation(int scoreChange, String mssg) async{
onSelectionAdded: _onSelectionAdded,
onSelectionRemoved: _onSelectionRemoved,
onStart: _onStartTask,
onSubtaskChanged: _onSubtaskChanged,
),
],
),
Expand Down
56 changes: 46 additions & 10 deletions lib/screens/task_box.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:taskly/google_calendar.dart';
import 'package:taskly/models/subtask.dart';
import 'package:taskly/models/task.dart';

class TaskBoxWidget extends StatelessWidget {
class TaskBoxWidget extends StatefulWidget {
final Task task;
final VoidCallback onEdit;
final VoidCallback onDelete;
final VoidCallback onClose;
final VoidCallback onStart;

const TaskBoxWidget({
super.key,
required this.task,
required this.onEdit,
required this.onDelete,
required this.onClose,
required this.onStart,
});

@override
State<TaskBoxWidget> createState() => _TaskBoxWidgetState();
}

class _TaskBoxWidgetState extends State<TaskBoxWidget> {
late Task task;
bool subtasksChangesDone = false;

@override
void initState() {
super.initState();
task = widget.task;
}

@override
Widget build(BuildContext context) {
return Dialog(
Expand Down Expand Up @@ -83,19 +96,40 @@ class TaskBoxWidget extends StatelessWidget {
if (task.dependency!.isCompleted)
const Text(
'Completed',
style: const TextStyle(
style: TextStyle(
fontSize: 16, color: Colors.green),
),
if (!task.dependency!.isCompleted)
const Text(
'Pending',
style: const TextStyle(
fontSize: 16, color: Colors.red),
style:
TextStyle(fontSize: 16, color: Colors.red),
),
],
),
const SizedBox(height: 20),

// Subtasks
ListView.builder(
shrinkWrap: true,
itemCount: task.subtasks.length,
itemBuilder: (context, index) {
Subtask s = task.subtasks[index];
return ListTile(
title: Text(s.title),
leading: Checkbox(
value: s.isCompleted,
onChanged: (value) {
subtasksChangesDone = true;
task.subtasks[index].isCompleted =
value ?? false;
setState(() {});
},
),
);
},
),

// Edit and Delete Buttons
Row(
mainAxisAlignment: MainAxisAlignment.end,
Expand All @@ -118,15 +152,15 @@ class TaskBoxWidget extends StatelessWidget {
if (!task.isCompleted)
IconButton(
icon: const Icon(Icons.play_arrow_rounded),
onPressed: onStart,
onPressed: widget.onStart,
),
IconButton(
icon: const Icon(Icons.edit, color: Colors.blue),
onPressed: onEdit,
onPressed: widget.onEdit,
),
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: onDelete,
onPressed: widget.onDelete,
),
],
),
Expand All @@ -148,7 +182,9 @@ class TaskBoxWidget extends StatelessWidget {
child: IconButton(
icon: const Icon(Icons.close),
iconSize: 16,
onPressed: onClose,
onPressed: () {
Navigator.pop(context, subtasksChangesDone);
},
),
),
),
Expand Down
44 changes: 38 additions & 6 deletions lib/screens/taskform_screen.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:taskly/models/subtask.dart';
import 'package:taskly/models/task.dart';
import 'package:taskly/service/speech_service.dart';
import 'package:taskly/constants.dart';
import 'package:taskly/storage/task_storage.dart';
import 'package:taskly/utils/date_utils.dart';
import 'package:taskly/widgets/repeat_select_card.dart';
import 'package:taskly/widgets/spacing.dart';
import 'package:taskly/widgets/subtask_add_card.dart';

class TaskFormScreen extends StatefulWidget {
final Task? task;
Expand Down Expand Up @@ -34,7 +37,7 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
int? repeatInterval;
Color defaultColor = Colors.blue;
late List<Task> _availableTasks;

late List<Subtask> subtasks;

bool isTitleListening = false;

Expand Down Expand Up @@ -62,6 +65,7 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
widget.task?.recurringDays == 0 ? null : widget.task?.recurringDays;

if (hasDeadline) deadline = widget.task?.deadline;
subtasks = widget.task?.subtasks ?? [];
setSelectedColour();
}

Expand All @@ -73,10 +77,11 @@ class _TaskFormScreenState extends State<TaskFormScreen> {
_focusNode.dispose();
super.dispose();
}
void setSelectedColour() async{
defaultColor = await DefaultTaskColorStorage.loadDefaultColor();
selectedColor = widget.task?.color ?? defaultColor;
}

void setSelectedColour() async {
defaultColor = await DefaultTaskColorStorage.loadDefaultColor();
selectedColor = widget.task?.color ?? defaultColor;
}

void _onTitleChanged() {
final input = _titleController.text.toLowerCase();
Expand Down Expand Up @@ -154,7 +159,7 @@ void setSelectedColour() async{
title: const Text('Pick Task Color'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: selectedColor?? defaultColor,
pickerColor: selectedColor ?? defaultColor,
onColorChanged: (color) {
setState(() {
selectedColor = color;
Expand Down Expand Up @@ -279,6 +284,13 @@ void setSelectedColour() async{
repeatTrailing = const Icon(Icons.repeat_rounded);
}

Widget subtaskTrailing;
if (subtasks.isEmpty) {
subtaskTrailing = const Icon(Icons.subdirectory_arrow_left_rounded);
} else {
subtaskTrailing = Text("${subtasks.length}");
}

return [
Card(
margin: const EdgeInsets.all(0),
Expand Down Expand Up @@ -345,6 +357,25 @@ void setSelectedColour() async{
},
),
),
const Spacing(),
Card(
margin: const EdgeInsets.all(0),
child: ListTile(
title: const Text("Subtasks"),
subtitle: const Text("Add subtasks to your task"),
trailing: subtaskTrailing,
onTap: () async {
List<Subtask>? sub = await showModalBottomSheet(
context: context,
builder: (context) => SubtaskAddCard(task: widget.task),
);
if (sub != null) {
subtasks = sub;
setState(() {});
}
},
),
),
];
}

Expand All @@ -359,6 +390,7 @@ void setSelectedColour() async{
recurringDays: repeatInterval,
color: selectedColor ?? defaultColor,
dependency: selectedDependency,
subtasks: subtasks,
);
Fluttertoast.showToast(
msg: editing
Expand Down
Loading