diff --git a/lib/add_task_dialog.dart b/lib/add_task_dialog.dart new file mode 100644 index 0000000..33534fb --- /dev/null +++ b/lib/add_task_dialog.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'task_provider.dart'; +import 'task_model.dart'; +import 'package:intl/intl.dart'; + +class AddTaskDialog extends StatefulWidget { + @override + _AddTaskDialogState createState() => _AddTaskDialogState(); +} + +class _AddTaskDialogState extends State { + final _formKey = GlobalKey(); + String _title = ''; + String? _description; + String? _category; + DateTime? _selectedDeadline; + TaskPriority _selectedPriority = TaskPriority.medium; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text('Add New Task'), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextFormField( + decoration: InputDecoration(labelText: 'Task Title*'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a task title'; + } + return null; + }, + onSaved: (value) { + _title = value ?? ''; + }, + ), + TextFormField( + decoration: InputDecoration(labelText: 'Description (optional)'), + onSaved: (value) { + _description = value; + }, + ), + TextFormField( + decoration: InputDecoration(labelText: 'Category (optional)'), + onSaved: (value) { + _category = value; + }, + ), + SizedBox(height: 20), + Text('Deadline: ${_selectedDeadline != null ? DateFormat('yMMMd').format(_selectedDeadline!) : 'No deadline'}'), + ElevatedButton( + child: Text('Select Deadline'), + onPressed: () => _selectDeadline(context), + ), + DropdownButton( + value: _selectedPriority, + onChanged: (TaskPriority? newValue) { + setState(() { + _selectedPriority = newValue!; + }); + }, + items: TaskPriority.values.map((TaskPriority priority) { + return DropdownMenuItem( + value: priority, + child: Text(priority.toString().split('.').last), + ); + }).toList(), + ), + ], + ), + ), + actions: [ + TextButton( + child: Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ElevatedButton( + child: Text('Add Task'), + onPressed: () { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + Provider.of(context, listen: false).addTask( + title: _title, + description: _description, + category: _category, + deadline: _selectedDeadline, + priority: _selectedPriority, + ); + Navigator.of(context).pop(); + } + }, + ), + ], + ); + } + + Future _selectDeadline(BuildContext context) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime.now(), + lastDate: DateTime(2100), + ); + if (picked != null) { + setState(() { + _selectedDeadline = picked; + }); + } + } +} \ No newline at end of file diff --git a/lib/datetime_extension.dart b/lib/datetime_extension.dart new file mode 100644 index 0000000..de1e1af --- /dev/null +++ b/lib/datetime_extension.dart @@ -0,0 +1,19 @@ +extension DateTimeFormatting on DateTime { + // Форматирует дату как "DD/MM/YYYY" + String toFormattedDate() { + return "${this.day.toString().padLeft(2, '0')}/${this.month.toString().padLeft(2, '0')}/${this.year}"; + } + + // Форматирует как "x дней осталось" + String toDaysLeft() { + final now = DateTime.now(); + final difference = this.difference(now).inDays; + if (difference < 0) { + return "Overdue by ${difference.abs()} days"; + } else if (difference == 0) { + return "Due today"; + } else { + return "Due in $difference days"; + } + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index d58ea17..ff65563 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,101 +1,29 @@ -import 'dart:math'; - import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'task_provider.dart'; +import 'task_list_screen.dart'; void main() { - runApp(const MyApp()); + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => TaskProvider()), + ], + child: MyApp(), + ), + ); } class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: 'ToDo List', debugShowCheckedModeBanner: false, theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - Color _color = Colors.orangeAccent; - - void _incrementCounter() { - setState(() { - _counter++; - _color = - Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: _color, - title: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Text( - 'Masenkin', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - Text( - 'Maksim Sergeevich', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - ), - ), - ], - ), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - if (_counter > 10) - Text( - 'STOP CLICKING! \nIT\'S NOT HAMSTER COMBAT', - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - backgroundColor: _color, - tooltip: 'Increment', - child: const Icon(Icons.mood), + primarySwatch: Colors.blue, ), + home: TaskListScreen(), ); } } diff --git a/lib/task_details_screen.dart b/lib/task_details_screen.dart new file mode 100644 index 0000000..02cb9bd --- /dev/null +++ b/lib/task_details_screen.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'task_model.dart'; +import 'datetime_extension.dart'; + +class TaskDetailsScreen extends StatelessWidget { + final Task task; + + TaskDetailsScreen({required this.task}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(task.title), + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Category: ${task.category ?? 'Not specified'}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + 'Priority: ${task.priority.name}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 8), + Text( + 'Deadline: ${task.deadline != null ? task.deadline!.toFormattedDate() : 'No deadline'}', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 16), + Text( + 'Description:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + Text( + task.description ?? 'No description available', + style: TextStyle(fontSize: 16), + ), + SizedBox(height: 16), + Text( + 'Completed: ${task.isCompleted ? "Yes" : "No"}', + style: TextStyle(fontSize: 16), + ), + ], + ), + ), + ); + } +} diff --git a/lib/task_list_screen.dart b/lib/task_list_screen.dart new file mode 100644 index 0000000..2991a0c --- /dev/null +++ b/lib/task_list_screen.dart @@ -0,0 +1,108 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'task_provider.dart'; +import 'task_details_screen.dart'; +import 'add_task_dialog.dart'; +import 'task_model.dart'; +import 'datetime_extension.dart'; + +class TaskListScreen extends StatefulWidget { + @override + _TaskListScreenState createState() => _TaskListScreenState(); +} + +class _TaskListScreenState extends State { + Color _color = Colors.transparent; + + @override + void initState() { + super.initState(); + Provider.of(context, listen: false).loadTasks(); + _color = + Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: _color, + title: Column( + crossAxisAlignment: + CrossAxisAlignment.start, // Чтобы текст выровнялся по левому краю + children: [ + Text('ToDo List', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), + Text( + 'made by Factorino', + style: TextStyle( + fontSize: 12, // Размер шрифта для подписи + fontStyle: FontStyle.italic, // Сделаем текст курсивом + ), + ), + ], + ), + actions: [ + IconButton( + icon: Icon(Icons.sort), + onPressed: () { + context.read().sortTasks(); + }, + ), + ], + ), + body: Consumer( + builder: (context, taskProvider, child) { + if (taskProvider.tasks.isEmpty) { + return Center(child: Text('No tasks yet.')); + } + + return ListView.builder( + itemCount: taskProvider.tasks.length, + itemBuilder: (context, index) { + Task task = taskProvider.tasks[index]; + return ListTile( + title: Text(task.title), + subtitle: Text( + '${task.category ?? 'No category'} - ${task.deadline?.toFormattedDate() ?? 'No deadline'}'), + trailing: Checkbox( + value: task.isCompleted, + onChanged: (value) { + context.read().toggleTaskCompletion(task); + }, + ), + onTap: () { + // Открываем экран с деталями задачи + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TaskDetailsScreen(task: task), + ), + ); + }, + onLongPress: () { + context.read().removeTask(task); + }, + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () => _openAddTaskDialog(context), + backgroundColor: _color, + child: Icon(Icons.add), + ), + ); + } + + void _openAddTaskDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return AddTaskDialog(); + }, + ); + } +} diff --git a/lib/task_model.dart b/lib/task_model.dart new file mode 100644 index 0000000..90467cb --- /dev/null +++ b/lib/task_model.dart @@ -0,0 +1,23 @@ +enum TaskPriority { + low, + medium, + high +} + +class Task { + final String title; + final String? description; + final String? category; + final DateTime? deadline; + bool isCompleted; + TaskPriority priority; + + Task({ + required this.title, + this.description, + this.category, + this.deadline, + this.isCompleted = false, + this.priority = TaskPriority.medium, + }); +} \ No newline at end of file diff --git a/lib/task_provider.dart b/lib/task_provider.dart new file mode 100644 index 0000000..4a9498a --- /dev/null +++ b/lib/task_provider.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'task_model.dart'; + +class TaskProvider with ChangeNotifier { + List _tasks = []; + + List get tasks => _tasks; + + // Добавление задачи + void addTask({ + required String title, + String? description, + String? category, + DateTime? deadline, + TaskPriority priority = TaskPriority.medium, + }) { + final task = Task( + title: title, + description: description, + category: category, + deadline: deadline, + priority: priority, + ); + _tasks.add(task); + notifyListeners(); + } + + // Удаление задачи + void removeTask(Task task) { + _tasks.remove(task); + notifyListeners(); + } + + // Изменение состояния задачи + void toggleTaskCompletion(Task task) { + task.isCompleted = !task.isCompleted; + notifyListeners(); + } + + // Сортировка задач по приоритету и дедлайну + void sortTasks() { + _tasks.sort((a, b) { + // Сравнение по приоритету + if (a.priority == b.priority) { + // Если оба дедлайна не null, сравниваем их + if (a.deadline != null && b.deadline != null) { + return a.deadline!.compareTo(b.deadline!); + } + // Если у одной задачи нет дедлайна, она считается "позднее" + if (a.deadline == null) return 1; // Задачи без дедлайна позже + if (b.deadline == null) return -1; // Задачи с дедлайном раньше + return 0; + } + // Сравнение по приоритету + return a.priority.index.compareTo(b.priority.index); + }); + notifyListeners(); + } + + // Метод для завершения всех задач + void completeAllTasks() { + for (var task in _tasks) { + task.isCompleted = true; + } + notifyListeners(); + } + + // Асинхронная загрузка задач + Future loadTasks() async { + if (_tasks.isEmpty) { + _tasks = [ + Task( + title: "Task 1", + category: "Work", + deadline: DateTime.now().add(Duration(days: 1)), + priority: TaskPriority.high), + Task( + title: "Task 2", + category: "Home", + deadline: DateTime.now().add(Duration(days: 2)), + priority: TaskPriority.medium), + Task( + title: "Task 3", + description: "Optional description", + priority: TaskPriority.low), + Task( + title: "Task 4", + category: "Personal", + priority: TaskPriority.high), + ]; + } + notifyListeners(); + } +} diff --git a/pubspec.lock b/pubspec.lock index 07514d0..8ad20e2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -75,6 +75,14 @@ packages: description: flutter source: sdk version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" + source: hosted + version: "0.17.0" leak_tracker: dependency: transitive description: @@ -131,6 +139,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -139,6 +155,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 3d616eb..57762cc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ environment: dependencies: flutter: sdk: flutter + provider: ^6.0.0 + intl: ^0.17.0 # The following adds the Cupertino Icons font to your application.