Siga essas instruções:
Neste trabalho vamos acrescentar duas animações na lista de tarefas:
Ao concluir uma tarefa, ela deve deslizar por sobre a lista de tarefas (8 pontos)
Uma opção simples é usar um Stack e um AnimatedPositioned
Pode usar as classes do tutorial de animação da aula de hoje.
Pode (deve!) usar a classe AnimatedList.
A tarefa concluída deve fazer um "tilt" ao ir para a posição, como se estivesse caindo um lado primeiro (inclinar e depois alinhar horizontalmente de novo). (2 pontos)
Essas animações devem ser feitas apenas ao concluir uma tarefa.
Não é necessário fazer a animação ao "desconcluir" uma tarefa (embora seja fácil...)
Não altere o comportamento do "deletar tarefa" (continua o desfazer com 3 segundos)
Se você colocou um "desfazer" de três segundos no concluir tarefa, tire - vai atrapalhar a animação!
Implemente o desconcluir tarefa! Deve apenas mover a tarefa para o final da lista de tarefas não concluída.

Esse é o código base. Preserve suas funcionalidades originais.

class TaskListScreen extends StatefulWidget {
  @override
  _TaskListScreenState createState() => _TaskListScreenState();
}

class _TaskListScreenState extends State<TaskListScreen> {
  final List<Task> taskList = [];
  final TextEditingController textController = TextEditingController();

  String userEmail = "";

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    userEmail = ModalRoute.of(context)?.settings.arguments as String;
    _loadTasks();
  }

  Future<void> _loadTasks() async {
    final url = Uri.https('barra.cos.ufrj.br:443', '/rest/tarefas');
    try {
      final response = await http.get(
        url,
        headers: {
          'Authorization': 'Bearer $jwtToken',
          'Content-Type': 'application/json',
        },
      );
    if (response.statusCode == 200) {
      final List<dynamic> tasksJson = jsonDecode(response.body);

      // Ensure the response has tasks, else initialize an empty list
      if (tasksJson.isNotEmpty && tasksJson[0]['valor'] != null) {
        final tasks = tasksJson[0]['valor'] as List;
        setState(() {
          taskList.clear();
          taskList.addAll(tasks.map((task) => Task.fromJson(task)).toList());
        });
      } else {
        setState(() {
          taskList.clear(); // Clear or initialize if list is empty
        });
      }
      } else if (response.statusCode == 401) {
        // Token expired or unauthorized
        _handleUnauthorized();
      } else {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text("Failed to load tasks")));
      }
    } catch (e) {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text("Error loading tasks: $e")));
    }
  }

  Future<void> _saveTasks() async {
    final url = Uri.https('barra.cos.ufrj.br:443', 'rest/tarefas');
    final tasksJson = jsonEncode({
      'email': userEmail, // Include the email in the JSON body
      'valor': taskList.map((task) => task.toJson()).toList(),
    });
    try {
      final response = await http.patch(
        url,
        headers: {
          'Authorization': 'Bearer $jwtToken',
          'Content-Type': 'application/json',
        },
        body: tasksJson,
      );

      if (response.statusCode != 204) {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text("Falha em salvar")));
      } else if (response.statusCode == 401) {
        // Token expired or unauthorized
        _handleUnauthorized();
      }
    } catch (e) {
      ScaffoldMessenger.of(context)
          .showSnackBar(SnackBar(content: Text("Erro")));
    }

    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text("Salvo com sucesso")));
  }

  void _addTask(String taskText) {
    setState(() {
      String trimmedText = taskText.trim();
      if (trimmedText.isNotEmpty && !_isDuplicate(trimmedText)) {
        taskList.insert(
            0, Task(titulo: trimmedText, ordem: taskList.length + 1));
        textController.clear();
      }
    });
  }

  bool _isDuplicate(String taskText) {
    return taskList
        .any((task) => task.titulo.toLowerCase() == taskText.toLowerCase());
  }

  void _deleteTask(int index) {
    setState(() {
      Task task = taskList[index];
      task.isDeleting = true;

      task.deletionTimer = Timer(const Duration(seconds: 3), () {
        setState(() {
          taskList.removeAt(index);
        });
      });
    });
  }

  void _undoDelete(int index) {
    setState(() {
      Task task = taskList[index];
      task.isDeleting = false;
      task.deletionTimer?.cancel();
      task.deletionTimer = null;
    });
  }

  void _toggleCompletion(int index, bool? value) {
    setState(() {
      Task task = taskList[index];
      task.concluida = value ?? false;
      task.completedAt = task.concluida ? DateTime.now() : null;
      _reorderTaskList();
    });
  }

  void _reorderTaskList() {
    taskList.sort((a, b) {
      if (a.concluida && !b.concluida) return 1;
      if (!a.concluida && b.concluida) return -1;
      return 0;
    });
  }

  void _handleUnauthorized() {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text("Sessão expirada. Por favor, faça login novamente.")));
    Navigator.pushNamedAndRemoveUntil(context, '/login', (route) => false);
  }

  void _showSnackBar(String taskText, int index) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Tarefa "$taskText" removida'),
        duration: const Duration(seconds: 3),
        action: SnackBarAction(
          label: 'Desfazer',
          onPressed: () {
            _undoDelete(index);
          },
        ),
      ),
    );
  }

  @override
  void dispose() {
    for (var task in taskList) {
      task.deletionTimer?.cancel();
    }
    textController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Lista de Tarefas'),
        foregroundColor: Colors.white,
        backgroundColor: Colors.blue,
        actions: [
          IconButton(
            icon: const Icon(Icons.save),
            onPressed: _saveTasks,
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: textController,
                    autofocus: true,
                    onSubmitted: _addTask,
                    decoration: const InputDecoration(
                      labelText: 'Adicionar nova tarefa',
                      border: OutlineInputBorder(),
                    ),
                  ),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: () => _addTask(textController.text),
                  child: const Icon(Icons.add),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.all(16),
                    shape: const CircleBorder(),
                  ),
                ),
              ],
            ),
            const SizedBox(height: 20),
            Expanded(
              child: Container(
                decoration: BoxDecoration(
                  border: Border.all(color: Colors.blueAccent),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: ListView.builder(
                  itemCount: taskList.length,
                  itemBuilder: (context, index) {
                    final task = taskList[index];
                    return _buildTaskItem(task, index);
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTaskItem(Task task, int index) {
    Color backgroundColor = index % 2 == 0 ? Colors.green[100]! : Colors.white;

    if (task.isDeleting) {
      backgroundColor = Colors.red[100]!;
    }

    return Dismissible(
      key: UniqueKey(),
      background: Container(
        color: Colors.green,
        alignment: Alignment.centerLeft,
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: const Icon(Icons.check, color: Colors.white),
      ),
      secondaryBackground: Container(
        color: Colors.red,
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.symmetric(horizontal: 20),
        child: const Icon(Icons.delete, color: Colors.white),
      ),
      confirmDismiss: (direction) async {
        if (direction == DismissDirection.endToStart) {
          _deleteTask(index);
          _showSnackBar(task.titulo, index);
          return false;
        } else if (direction == DismissDirection.startToEnd) {
          if (!task.concluida) {
            _toggleCompletion(index, true);
          }
          return false;
        }
        return false;
      },
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
        padding: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          color: backgroundColor,
          borderRadius: BorderRadius.circular(8),
          boxShadow: [
            BoxShadow(
              color: Colors.grey.withOpacity(0.2),
              spreadRadius: 1,
              blurRadius: 3,
              offset: const Offset(0, 1),
            ),
          ],
        ),
        child: ListTile(
          leading: Checkbox(
            value: task.concluida,
            onChanged: (value) => _toggleCompletion(index, value),
          ),
          title: Text(
            task.titulo,
            style: TextStyle(
              decoration: task.concluida
                  ? TextDecoration.lineThrough
                  : TextDecoration.none,
              color: task.concluida ? Colors.grey : Colors.black,
            ),
          ),
          trailing: task.isDeleting
              ? TextButton(
                  onPressed: () => _undoDelete(index),
                  child: const Text(
                    'Desfazer',
                    style: TextStyle(color: Colors.blue),
                  ),
                )
              : IconButton(
                  icon: const Icon(Icons.delete, color: Colors.red),
                  onPressed: () => _deleteTask(index),
                ),
        ),
      ),
    );
  }
}

class Task {
  String titulo;
  int ordem;
  bool concluida;
  DateTime? completedAt;
  bool isDeleting = false;
  Timer? deletionTimer;

  Task(
      {required this.titulo,
      required this.ordem,
      this.concluida = false,
      this.completedAt});

  factory Task.fromJson(Map<String, dynamic> json) {
    return Task(
      titulo: json['titulo'] as String,
      concluida: json['concluida'] as bool? ?? false,
      ordem: json['ordem'] as int,
    );
  }
  Map<String, dynamic> toJson() {
    return {
      'titulo': titulo,
      'concluida': concluida,
      'ordem': ordem,
    };
  }
}