قائمة المهام: التراجع عن الحذف
جزء من قسم المشاريع في رحلة Flutter على Coddy. الدرس 7 من 36.
السحب سريع، وكذلك السحب عن طريق الخطأ. تتيح لك التطبيقات الجيدة التراجع عنه: تظهر رسالة في الأسفل توضّح ما حُذف، وبجانبها زر «Undo».
يأخذ SnackBar وسيطًا هو action:، وهو SnackBarAction يتضمن label وonPressed. لإعادة المهمة إلى موضعها، تذكّر موضعها قبل إزالتها:
final index = _tasks.indexOf(task);
// أزِلها، ثم أظهِر SnackBar
SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
_tasks.insert(index, task);
});
},
)إذا سحبت مهمتين بسرعة، تنتظر الرسالة الثانية حتى تنتهي مهلة الرسالة الأولى. استدعِ ScaffoldMessenger.of(context).hideCurrentSnackBar(); قبل showSnackBar، لكي تظهر الرسالة الأحدث فورًا.
تعيد insert(index, task) المهمة إلى ذلك الموضع، وتنقل المهام التي تليها إلى الأسفل. أما add فتعيدها إلى النهاية.
الدالة التي تمررها إلى onPressed تتذكر index وtask منذ لحظة إنشائها، حتى إن نُفذت لاحقًا. وهذا ما يُسمى بالإغلاق، وتعتمد عليه دوال رد الاتصال طوال الوقت.
التحدي
متوسطأتِح التراجع عن الحذف. في _deleteTask:
- قبل إزالة المهمة، احتفظ بموضعها باستخدام
indexOf - بعد إزالتها، اعرض
SnackBarبالنصDeletedوعنوان المهمة، مثلDeleted Walk the dog - أضِف إليه إجراء
Undoيعيد المهمة إلى موضعها السابق - أخفِ الرسالة المعروضة أولًا، كي يحل حذف جديد محلها
جرّب بنفسك
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
),
home: const TodoScreen(),
);
}
}
class Task {
Task(this.title);
final String title;
bool done = false;
}
class TodoScreen extends StatefulWidget {
const TodoScreen({super.key});
@override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final List<Task> _tasks = [Task('Buy milk'), Task('Walk the dog'), Task('Read a book')];
final _controller = TextEditingController();
int get _left => _tasks.where((task) => !task.done).length;
void _addTask() {
final title = _controller.text.trim();
if (title.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Type a task first')),
);
return;
}
setState(() {
_tasks.add(Task(title));
});
_controller.clear();
}
void _deleteTask(Task task) {
setState(() {
_tasks.remove(task);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Tasks')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'New task',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed: _addTask,
icon: const Icon(Icons.add),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Text(
_left == 1 ? '1 task left' : '$_left tasks left',
style: Theme.of(context).textTheme.titleSmall,
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: _tasks.length,
itemBuilder: (context, index) {
final task = _tasks[index];
return Dismissible(
key: ObjectKey(task),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 24),
child: const Icon(Icons.delete, color: Colors.white),
),
onDismissed: (direction) => _deleteTask(task),
child: ListTile(
leading: Checkbox(
value: task.done,
onChanged: (value) {
setState(() {
task.done = value!;
});
},
),
title: Text(
task.title,
style: task.done
? const TextStyle(
decoration: TextDecoration.lineThrough,
color: Colors.grey,
)
: null,
),
),
);
},
),
),
],
),
);
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس المشاريع
تدرّب بنفسك: مترجم Flutter عبر الإنترنت