مشروع تخرج الميزات المتقدمة: بناء تطبيق سطر أوامر
نظرة عامة على مشروع التخرج
في هذا الدرس الأخير من دروس ميزات Dart المتقدمة، ستبني تطبيق سطر أوامر كاملاً يُسمى TaskFlow — أداة لإدارة المهام والإنتاجية. يجمع هذا المشروع كل ميزة متقدمة تعلمتها خلال هذا الدرس التعليمي في تطبيق واحد متماسك.
TaskFlow هو تطبيق CLI يتيح للمستخدمين إدارة المهام وجلب اقتباسات تحفيزية من API وإنشاء تقارير وحفظ البيانات في ملفات. يمارس ميزات Dart المتقدمة التالية:
- Async/Await — لاستدعاءات API وإدخال/إخراج الملفات
- التدفقات — لتحديثات التقدم في الوقت الفعلي ومراقبة الأحداث
- العوازل — لتوليد التقارير كثيفة المعالجة
- السجلات والتفكيك — لقيم الإرجاع المنظمة
- مطابقة الأنماط — لتوجيه الأوامر والتحقق من البيانات
- البرمجة الوظيفية — لتحويلات البيانات وخطوط الأنابيب
- إدخال/إخراج الملفات و JSON — لاستمرارية البيانات
- الحزم والمكتبات — لتنظيم الكود النمطي
هيكل المشروع
هيكل المشروع المنظم جيداً هو أساس البرمجيات القابلة للصيانة. يتبع TaskFlow اتفاقيات حزم Dart مع فصل واضح للمسؤوليات.
تخطيط مشروع TaskFlow
taskflow/
bin/
taskflow.dart # نقطة الدخول (الدالة الرئيسية)
lib/
taskflow.dart # ملف البرميل (الواجهة العامة)
src/
models/
task.dart # نموذج بيانات المهمة
report.dart # نموذج بيانات التقرير
services/
task_service.dart # منطق الأعمال للمهام
api_service.dart # عميل HTTP API
report_service.dart # توليد التقارير (مع العوازل)
storage/
file_storage.dart # استمرارية ملفات JSON
cli/
command_router.dart # موجه أوامر بمطابقة الأنماط
display.dart # تنسيق مخرجات الطرفية
utils/
extensions.dart # امتدادات String و DateTime
validators.dart # التحقق من المدخلات
test/
task_service_test.dart
file_storage_test.dart
pubspec.yaml
الخطوة 1: نماذج البيانات مع السجلات
نبدأ بتحديد نماذج البيانات الأساسية. نموذج Task يستخدم السجلات للبيانات الوصفية المنظمة، ويتضمن تسلسل JSON لاستمرارية الملفات.
نموذج المهمة (lib/src/models/task.dart)
import 'dart:convert';
/// مستويات الأولوية للمهام.
enum Priority { low, medium, high, critical }
/// حالة المهمة خلال دورة حياتها.
enum TaskStatus { pending, inProgress, completed, cancelled }
/// يمثل مهمة واحدة مع بيانات وصفية.
class Task {
final String id;
final String title;
final String description;
final Priority priority;
final TaskStatus status;
final DateTime createdAt;
final DateTime? completedAt;
final List<String> tags;
final Duration estimatedDuration;
Task({
required this.id,
required this.title,
this.description = '',
this.priority = Priority.medium,
this.status = TaskStatus.pending,
DateTime? createdAt,
this.completedAt,
this.tags = const [],
this.estimatedDuration = const Duration(hours: 1),
}) : createdAt = createdAt ?? DateTime.now();
/// إنشاء نسخة معدّلة من هذه المهمة.
Task copyWith({
String? title,
String? description,
Priority? priority,
TaskStatus? status,
DateTime? completedAt,
List<String>? tags,
Duration? estimatedDuration,
}) {
return Task(
id: id,
title: title ?? this.title,
description: description ?? this.description,
priority: priority ?? this.priority,
status: status ?? this.status,
createdAt: createdAt,
completedAt: completedAt ?? this.completedAt,
tags: tags ?? this.tags,
estimatedDuration: estimatedDuration ?? this.estimatedDuration,
);
}
/// إرجاع سجل ملخص مع معلومات المهمة الرئيسية.
({String title, Priority priority, TaskStatus status, int daysOld})
get summary => (
title: title,
priority: priority,
status: status,
daysOld: DateTime.now().difference(createdAt).inDays,
);
/// تسلسل JSON.
factory Task.fromJson(Map<String, dynamic> json) => Task(
id: json['id'] as String,
title: json['title'] as String,
description: json['description'] as String? ?? '',
priority: Priority.values.byName(json['priority'] as String),
status: TaskStatus.values.byName(json['status'] as String),
createdAt: DateTime.parse(json['created_at'] as String),
completedAt: json['completed_at'] != null
? DateTime.parse(json['completed_at'] as String)
: null,
tags: (json['tags'] as List<dynamic>?)?.cast<String>() ?? [],
estimatedDuration:
Duration(minutes: json['estimated_minutes'] as int? ?? 60),
);
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'description': description,
'priority': priority.name,
'status': status.name,
'created_at': createdAt.toIso8601String(),
'completed_at': completedAt?.toIso8601String(),
'tags': tags,
'estimated_minutes': estimatedDuration.inMinutes,
};
@override
String toString() => 'Task($title, $priority, $status)';
}
الخطوة 2: تخزين الملفات مع استمرارية JSON
تتعامل طبقة التخزين مع قراءة وكتابة المهام في ملف JSON. تستخدم إدخال/إخراج الملفات غير المتزامن وأنماط الكتابة الذرية من الدرس 13.
خدمة تخزين الملفات (lib/src/storage/file_storage.dart)
import 'dart:io';
import 'dart:convert';
import '../models/task.dart';
/// يحفظ المهام في ملف JSON على القرص.
class FileStorage {
final String filePath;
FileStorage(this.filePath);
/// تحميل جميع المهام من ملف JSON.
Future<List<Task>> loadTasks() async {
final file = File(filePath);
if (!await file.exists()) return [];
try {
final contents = await file.readAsString();
if (contents.trim().isEmpty) return [];
final jsonList = jsonDecode(contents) as List<dynamic>;
return jsonList
.map((json) => Task.fromJson(json as Map<String, dynamic>))
.toList();
} on FormatException catch (e) {
print('Warning: Corrupted data file. Starting fresh. ($e)');
return [];
}
}
/// حفظ جميع المهام في ملف JSON (كتابة ذرية).
Future<void> saveTasks(List<Task> tasks) async {
final file = File(filePath);
await file.parent.create(recursive: true);
final jsonList = tasks.map((t) => t.toJson()).toList();
final jsonString = const JsonEncoder.withIndent(' ').convert(jsonList);
// كتابة ذرية: الكتابة لملف مؤقت ثم إعادة التسمية
final tempFile = File('$filePath.tmp');
await tempFile.writeAsString(jsonString);
await tempFile.rename(filePath);
}
}
الخطوة 3: خدمة API مع Async/Await
تجلب خدمة API اقتباسات تحفيزية من API خارجي. توضح async/await ومعالجة الأخطاء وتحليل JSON من استجابات الشبكة.
خدمة API (lib/src/services/api_service.dart)
import 'dart:io';
import 'dart:convert';
import 'dart:async';
/// تجلب البيانات من واجهات برمجة التطبيقات الخارجية.
class ApiService {
final HttpClient _client;
final Duration _timeout;
ApiService({Duration timeout = const Duration(seconds: 10)})
: _client = HttpClient(),
_timeout = timeout {
_client.connectionTimeout = _timeout;
}
/// جلب اقتباس تحفيزي.
/// يعيد سجلاً مع نص الاقتباس والمؤلف.
Future<({String text, String author})> fetchQuote() async {
try {
final uri = Uri.parse('https://api.quotable.io/random');
final request = await _client.getUrl(uri);
final response = await request.close().timeout(_timeout);
if (response.statusCode != 200) {
return (text: 'Stay focused and never give up!', author: 'Unknown');
}
final body = await response.transform(utf8.decoder).join();
final json = jsonDecode(body) as Map<String, dynamic>;
return (
text: json['content'] as String? ?? 'Keep going!',
author: json['author'] as String? ?? 'Unknown',
);
} on TimeoutException {
return (text: 'Time is precious. Use it wisely.', author: 'System');
} on SocketException {
return (text: 'Work offline. Stay productive.', author: 'System');
} catch (e) {
return (text: 'Every step forward counts.', author: 'System');
}
}
void close() => _client.close();
}
({String text, String author}) كأنواع إرجاع. هذا يتجنب إنشاء فئة كاملة لمجرد قيمة إرجاع منظمة بسيطة. السجلات مثالية لتجميع البيانات الخفيفة في طرق الخدمة.الخطوة 4: خدمة المهام مع البرمجة الوظيفية
تحتوي خدمة المهام على منطق الأعمال الأساسي. تستخدم أنماط البرمجة الوظيفية (map و where و fold والتسلسل) لتحويل واستعلام بيانات المهام.
خدمة المهام (lib/src/services/task_service.dart)
import 'dart:math';
import '../models/task.dart';
import '../storage/file_storage.dart';
/// منطق الأعمال الأساسي لإدارة المهام.
class TaskService {
final FileStorage _storage;
List<Task> _tasks = [];
TaskService(this._storage);
List<Task> get tasks => List.unmodifiable(_tasks);
Future<void> initialize() async {
_tasks = await _storage.loadTasks();
}
Future<void> save() async {
await _storage.saveTasks(_tasks);
}
Future<Task> addTask({
required String title,
Priority priority = Priority.medium,
List<String> tags = const [],
}) async {
final task = Task(
id: 'task_${DateTime.now().millisecondsSinceEpoch}_${Random().nextInt(9999)}',
title: title,
priority: priority,
tags: tags,
);
_tasks.add(task);
await save();
return task;
}
/// تحديث حالة المهمة باستخدام مطابقة الأنماط.
Future<Task?> updateStatus(String id, TaskStatus newStatus) async {
final index = _tasks.indexWhere((t) => t.id == id);
if (index == -1) return null;
final task = _tasks[index];
final updated = switch (newStatus) {
TaskStatus.completed => task.copyWith(
status: newStatus, completedAt: DateTime.now()),
TaskStatus.pending => task.copyWith(
status: newStatus, completedAt: null),
_ => task.copyWith(status: newStatus),
};
_tasks[index] = updated;
await save();
return updated;
}
// ===== استعلامات البرمجة الوظيفية =====
List<Task> byStatus(TaskStatus status) =>
_tasks.where((t) => t.status == status).toList();
List<Task> search(String query) {
final lowerQuery = query.toLowerCase();
return _tasks.where((t) =>
t.title.toLowerCase().contains(lowerQuery)).toList();
}
List<Task> sorted() {
return [..._tasks]..sort((a, b) {
final p = b.priority.index.compareTo(a.priority.index);
return p != 0 ? p : a.createdAt.compareTo(b.createdAt);
});
}
Duration get totalEstimatedTime =>
_tasks.fold(Duration.zero, (sum, t) => sum + t.estimatedDuration);
double get completionRate {
if (_tasks.isEmpty) return 0;
return _tasks.where((t) => t.status == TaskStatus.completed).length /
_tasks.length;
}
Set<String> get allTags => _tasks.expand((t) => t.tags).toSet();
/// خط أنابيب: الحصول على المهام العاجلة عالية الأولوية المتأخرة.
List<Task> get urgentTasks => _tasks
.where((t) => t.status == TaskStatus.pending)
.where((t) =>
t.priority == Priority.high || t.priority == Priority.critical)
.where((t) => DateTime.now().difference(t.createdAt).inDays > 3)
.toList()
..sort((a, b) => a.createdAt.compareTo(b.createdAt));
}
الخطوة 5: توليد التقارير مع العوازل
يمكن أن يكون توليد التقارير مكلفاً حسابياً لمجموعات البيانات الكبيرة. نفرّغه لعازل للحفاظ على استجابة الخيط الرئيسي، ونستخدم تدفقاً للإبلاغ عن التقدم.
خدمة التقارير (lib/src/services/report_service.dart)
import 'dart:async';
import 'dart:isolate';
import 'dart:convert';
import '../models/task.dart';
import '../models/report.dart';
/// تولّد التقارير باستخدام العوازل للحسابات الثقيلة.
class ReportService {
/// توليد تقرير في عازل خلفي.
Stream<String> generateReport(List<Task> tasks) async* {
yield 'Starting report generation...';
if (tasks.length < 100) {
yield 'Processing ${tasks.length} tasks...';
final report = _computeReport(tasks.map((t) => t.toJson()).toList());
yield 'Report generated successfully.';
yield 'REPORT_JSON:${jsonEncode(report.toJson())}';
return;
}
yield 'Large dataset detected. Using background processing...';
final receivePort = ReceivePort();
final taskJsonList = tasks.map((t) => t.toJson()).toList();
await Isolate.spawn(
_isolateEntryPoint,
(receivePort.sendPort, taskJsonList),
);
await for (final message in receivePort) {
if (message is String) {
yield message;
if (message.startsWith('REPORT_JSON:')) {
receivePort.close();
break;
}
}
}
}
static void _isolateEntryPoint(
(SendPort sendPort, List<Map<String, dynamic>> taskData) args,
) {
final (sendPort, taskData) = args;
sendPort.send('Processing ${taskData.length} tasks in isolate...');
final report = _computeReport(taskData);
sendPort.send('REPORT_JSON:${jsonEncode(report.toJson())}');
}
static TaskReport _computeReport(List<Map<String, dynamic>> taskData) {
final tasks = taskData.map((j) => Task.fromJson(j)).toList();
// ... حسابات الإحصائيات ...
return TaskReport(
generatedAt: DateTime.now(),
totalTasks: tasks.length,
completedTasks: tasks.where((t) => t.status == TaskStatus.completed).length,
pendingTasks: tasks.where((t) => t.status == TaskStatus.pending).length,
overdueTasks: 0,
tasksByPriority: {},
tasksByTag: {},
totalEstimatedTime: tasks.fold(Duration.zero, (s, t) => s + t.estimatedDuration),
completionRate: tasks.isEmpty ? 0 :
tasks.where((t) => t.status == TaskStatus.completed).length / tasks.length,
);
}
}
final (sendPort, taskData) = args;. البيانات المُمررة للعازل يجب أن تكون قابلة للتسلسل (أوليات وقوائم وخرائط)، لذا نحوّل المهام لخرائط JSON قبل الإرسال ونعيد بناءها داخل العازل.الخطوة 6: موجه الأوامر مع مطابقة الأنماط
يحلل موجه الأوامر إدخال المستخدم ويوجّه للمعالج المناسب. يستخدم مطابقة أنماط Dart 3 بشكل مكثف لتحليل أوامر نظيف ومعبّر.
موجه الأوامر (lib/src/cli/command_router.dart)
import '../models/task.dart';
/// فئة مختومة تمثل جميع الأوامر الممكنة.
sealed class Command {}
class AddCommand extends Command {
final String title;
final Priority priority;
final List<String> tags;
AddCommand(this.title, this.priority, this.tags);
}
class ListCommand extends Command {
final TaskStatus? filterStatus;
ListCommand([this.filterStatus]);
}
class CompleteCommand extends Command {
final String taskId;
CompleteCommand(this.taskId);
}
class SearchCommand extends Command {
final String query;
SearchCommand(this.query);
}
class ReportCommand extends Command {}
class QuoteCommand extends Command {}
class HelpCommand extends Command {}
class ExitCommand extends Command {}
class UnknownCommand extends Command {
final String input;
UnknownCommand(this.input);
}
/// يحلل إدخال المستخدم إلى كائنات Command باستخدام مطابقة الأنماط.
class CommandParser {
Command parse(String input) {
final parts = input.trim().split(RegExp(r'\s+'));
if (parts.isEmpty || parts.first.isEmpty) return HelpCommand();
return switch (parts) {
['add', ...final rest] => _parseAdd(rest),
['list'] => ListCommand(),
['list', final status] => _parseList(status),
['complete', final id] => CompleteCommand(id),
['search', ...final rest] => SearchCommand(rest.join(' ')),
['report'] => ReportCommand(),
['quote'] => QuoteCommand(),
['help'] => HelpCommand(),
['exit'] || ['quit'] => ExitCommand(),
_ => UnknownCommand(input),
};
}
AddCommand _parseAdd(List<String> args) {
var priority = Priority.medium;
final tags = <String>[];
final titleParts = <String>[];
for (final arg in args) {
if (arg.startsWith('-p=')) {
priority = switch (arg.split('=').last) {
'low' || 'l' => Priority.low,
'high' || 'h' => Priority.high,
'critical' || 'c' => Priority.critical,
_ => Priority.medium,
};
} else if (arg.startsWith('-t=')) {
tags.add(arg.split('=').last);
} else {
titleParts.add(arg);
}
}
return AddCommand(titleParts.join(' '), priority, tags);
}
ListCommand _parseList(String status) {
final taskStatus = switch (status) {
'pending' => TaskStatus.pending,
'active' => TaskStatus.inProgress,
'completed' || 'done' => TaskStatus.completed,
_ => null,
};
return ListCommand(taskStatus);
}
}
switch في التنفيذ لا يحتاج حالة default. لأن Command فئة sealed، يعرف مُجمّع Dart جميع الأنواع الفرعية الممكنة ويتحقق من أن switch شامل. إذا أضفت فئة أوامر فرعية جديدة لاحقاً، سيُنبّه المُجمّع كل switch لا يعالجها.الخطوة 7: تنسيق العرض مع الامتدادات
تتعامل وحدة العرض مع جميع تنسيقات مخرجات الطرفية، باستخدام امتدادات لإضافة طرق مفيدة للأنواع الموجودة.
العرض والامتدادات
// ====== lib/src/utils/extensions.dart ======
extension StringExt on String {
String get capitalized {
if (isEmpty) return this;
return '${this[0].toUpperCase()}${substring(1)}';
}
String fixedWidth(int width) {
if (length >= width) return substring(0, width);
return padRight(width);
}
}
extension DateTimeExt on DateTime {
String get timeAgo {
final diff = DateTime.now().difference(this);
if (diff.inDays > 0) return '${diff.inDays}d ago';
if (diff.inHours > 0) return '${diff.inHours}h ago';
if (diff.inMinutes > 0) return '${diff.inMinutes}m ago';
return 'just now';
}
}
// ====== lib/src/cli/display.dart ======
import '../models/task.dart';
import '../utils/extensions.dart';
/// تنسّق وتطبع المخرجات للطرفية.
class Display {
void banner() {
print('==========================================');
print(' TaskFlow - Advanced Task Manager');
print('==========================================');
}
void taskList(List<Task> tasks, {String? filter}) {
if (tasks.isEmpty) {
print('No tasks yet.');
return;
}
for (final task in tasks) {
final status = switch (task.status) {
TaskStatus.pending => '[ ]',
TaskStatus.inProgress => '[~]',
TaskStatus.completed => '[x]',
TaskStatus.cancelled => '[-]',
};
final priority = switch (task.priority) {
Priority.critical => '!!!',
Priority.high => '!! ',
Priority.medium => '! ',
Priority.low => '. ',
};
print(' $status $priority ${task.title.fixedWidth(40)} '
'${task.createdAt.timeAgo}');
}
}
void help() {
print('Available Commands:');
print(' add <title> [-p=priority] [-t=tag]');
print(' list [status] | complete <id>');
print(' search <query> | report | quote');
print(' help | exit');
}
}
الخطوة 8: نقطة الدخول الرئيسية — تجميع كل شيء
تربط نقطة الدخول جميع المكونات معاً وتشغّل حلقة الأوامر التفاعلية.
نقطة دخول التطبيق (bin/taskflow.dart)
import 'dart:io';
Future<void> main(List<String> arguments) async {
// تهيئة الخدمات
final storage = FileStorage('data/tasks.json');
final taskService = TaskService(storage);
final apiService = ApiService();
final reportService = ReportService();
final display = Display();
final parser = CommandParser();
// تحميل المهام الموجودة
await taskService.initialize();
// عرض لافتة الترحيب
display.banner();
print('Loaded ${taskService.tasks.length} tasks.');
// جلب اقتباس تحفيزي عند البدء
final quote = await apiService.fetchQuote();
print('"${quote.text}" - ${quote.author}');
// حلقة الأوامر التفاعلية
var running = true;
while (running) {
stdout.write('taskflow> ');
final input = stdin.readLineSync()?.trim() ?? '';
if (input.isEmpty) continue;
final command = parser.parse(input);
// تنفيذ الأوامر مع مطابقة الأنماط...
if (command is ExitCommand) {
print('Goodbye! Your tasks are saved.');
running = false;
}
}
apiService.close();
}
الخطوة 9: مراقب التقدم المبني على التدفق
لنضف ميزة مبنية على التدفق تراقب تغييرات المهام في الوقت الفعلي. يوضح هذا متحكمات التدفق والتدفقات البثية.
تدفق أحداث تغييرات المهام
import 'dart:async';
import '../models/task.dart';
enum TaskEventType { added, updated, deleted, completed }
class TaskEvent {
final TaskEventType type;
final Task task;
final DateTime timestamp;
TaskEvent(this.type, this.task) : timestamp = DateTime.now();
}
/// مزيج يضيف بث الأحداث لأي خدمة.
mixin TaskEventEmitter {
final _controller = StreamController<TaskEvent>.broadcast();
Stream<TaskEvent> get events => _controller.stream;
void emit(TaskEventType type, Task task) {
_controller.add(TaskEvent(type, task));
}
void disposeEvents() => _controller.close();
}
// الاستخدام: أضف المزيج لـ TaskService
// class TaskService with TaskEventEmitter { ... }
// ثم: taskService.events
// .where((e) => e.type == TaskEventType.completed)
// .listen((e) => print('Completed: ${e.task.title}'));
الخطوة 10: التحقق من المدخلات
التحقق القوي من المدخلات يستخدم مطابقة الأنماط وعبارات الحراسة لضمان سلامة البيانات.
المدققات (lib/src/utils/validators.dart)
/// نتيجة التحقق باستخدام فئة مختومة.
sealed class ValidationResult {}
class Valid extends ValidationResult {}
class Invalid extends ValidationResult {
final String message;
Invalid(this.message);
}
class Validators {
static ValidationResult validateTitle(String title) => switch (title) {
'' => Invalid('Title cannot be empty.'),
String s when s.length < 3 =>
Invalid('Title must be at least 3 characters.'),
String s when s.length > 200 =>
Invalid('Title must be under 200 characters.'),
_ => Valid(),
};
static Priority? parsePriority(String input) => switch (input.toLowerCase()) {
'low' || 'l' => Priority.low,
'medium' || 'm' => Priority.medium,
'high' || 'h' => Priority.high,
'critical' || 'c' => Priority.critical,
_ => null,
};
}
// الاستخدام مع مطابقة الأنماط على النتيجة:
void addTask(String title) {
switch (Validators.validateTitle(title)) {
case Valid():
print('Creating task: $title');
case Invalid(:final message):
print('Validation error: $message');
}
}
ملخص الميزات: المفاهيم المتقدمة المُستخدمة
إليك ملخص لكل ميزة Dart متقدمة تم تطبيقها في مشروع التخرج هذا وأين تظهر:
خريطة الميزات
// الميزة | أين استُخدمت
// ========================= | ========================================
// async/await | ApiService، FileStorage، TaskService
// التدفقات | ReportService، TaskEventEmitter
// العوازل | ReportService (تقارير البيانات الكبيرة)
// السجلات | أنواع إرجاع ApiService، Task.summary
// مطابقة الأنماط | CommandParser، Validators، Display
// الفئات المختومة | Command، ValidationResult
// البرمجة الوظيفية | استعلامات TaskService
// إدخال/إخراج الملفات | FileStorage (قراءة وكتابة ذرية)
// معالجة JSON | Task.fromJson/toJson، التقارير
// الامتدادات | StringExt، DateTimeExt
// الحزم/المكتبات | نمط ملف البرميل، تنظيم src/
تشغيل واختبار التطبيق
جلسة نموذجية
$ dart run bin/taskflow.dart
==========================================
TaskFlow - Advanced Task Manager
==========================================
Loaded 0 tasks.
"The only way to do great work is to love what you do." - Steve Jobs
taskflow> add Build login page -p=high -t=frontend -t=auth
Task added: "Build login page" [high]
taskflow> add Fix critical bug -p=critical -t=security
Task added: "Fix critical bug" [critical]
taskflow> list
[ ] !!! Fix critical bug just now
[ ] !! Build login page just now
taskflow> report
[info] Processing 2 tasks...
========== TASK REPORT ==========
Total Tasks: 2
Completion Rate: 0.0%
================================
taskflow> exit
Goodbye! Your tasks are saved.
الخطوات التالية والتحسينات
لديك الآن أساس قوي لتطبيق Dart CLI. إليك أفكاراً لتوسيعه أكثر:
- إضافة
json_serializable— استبدالfromJson/toJsonالمكتوبة يدوياً بتوليد الكود - إضافة
freezed— جعل Task فئة بيانات غير قابلة للتغيير - إضافة مهام فرعية — تسلسلات مهام متداخلة مع معالجة متكررة
- إضافة تواريخ استحقاق — تتبع المواعيد النهائية مع تذكيرات مبنية على التدفق
- التصدير إلى HTML — إنشاء تقارير HTML منسّقة
- إضافة اختبارات الوحدة — اختبار كل خدمة بشكل مستقل
- النشر كحزمة — مشاركة أداة CLI على pub.dev
الملخص
تهانينا على إكمال درس ميزات Dart المتقدمة! في مشروع التخرج هذا، بنيت تطبيق CLI كاملاً يجمع:
- Async/Await لاستدعاءات API غير المحجوبة وعمليات إدخال/إخراج الملفات
- التدفقات للإبلاغ عن التقدم في الوقت الفعلي ومراقبة الأحداث
- العوازل لتوليد التقارير كثيفة المعالجة بدون حجب واجهة المستخدم
- السجلات لبيانات منظمة خفيفة مثل استجابات API وملخصات المهام
- مطابقة الأنماط لتوجيه الأوامر والتحقق وتنسيق العرض
- الفئات المختومة لمعالجة الأوامر الشاملة وأنواع النتائج
- البرمجة الوظيفية لاستعلامات البيانات والتحويلات وخطوط الأنابيب
- إدخال/إخراج الملفات و JSON للتخزين المستمر مع الكتابة الذرية
- الامتدادات لإضافة طرق مساعدة لـ String و DateTime
- هيكل الحزم لتنظيم كود نمطي وقابل للصيانة
لديك الآن مهارات Dart المتقدمة اللازمة لبناء تطبيقات متطورة. سواء انتقلت لتطوير Flutter للجوال أو Dart للخادم أو أدوات سطر الأوامر، ستخدمك هذه الأنماط جيداً.