diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index b893d03e95d..d7912b40bd0 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -54,6 +54,7 @@ export default defineConfig({ { label: 'Introduction', translations: { + ar: 'المقدمة', 'zh-CN': '介绍', fa: 'مقدمه', es: 'Introducción', @@ -66,6 +67,7 @@ export default defineConfig({ label: 'Getting Started', link: '/getting-started/', translations: { + ar: 'ابدأ الآن', 'zh-CN': '快速入门', fa: 'شروع شدن', es: 'Empezando', @@ -78,6 +80,7 @@ export default defineConfig({ label: 'Why Bloc?', link: '/why-bloc/', translations: { + ar: 'لماذا Bloc؟', 'zh-CN': '为什么用 Bloc?', fa: 'چرا Bloc؟', es: '¿Por qué Bloc?', @@ -90,6 +93,7 @@ export default defineConfig({ label: 'Bloc Concepts', link: '/bloc-concepts/', translations: { + ar: 'مفاهيم Bloc', 'zh-CN': 'Bloc 核心概念', fa: 'مفاهیم Bloc', es: 'Conceptos de Bloc', @@ -102,6 +106,7 @@ export default defineConfig({ label: 'Flutter Bloc Concepts', link: '/flutter-bloc-concepts/', translations: { + ar: 'مفاهيم Flutter Bloc', 'zh-CN': 'Flutter Bloc 核心概念', fa: 'مفاهیم Bloc فلاتر', es: 'Conceptos de Flutter Bloc', @@ -114,6 +119,7 @@ export default defineConfig({ label: 'Architecture', link: '/architecture/', translations: { + ar: 'البنية المعمارية', fa: 'معماری', es: 'Arquitectura', ja: 'アーキテクチャー', @@ -125,6 +131,7 @@ export default defineConfig({ label: 'Modeling State', link: '/modeling-state/', translations: { + ar: 'نمذجة الحالة', fa: 'حالت (State) مدل سازی', es: 'Modelando el Estado', ja: '状態のモデリング', @@ -136,6 +143,7 @@ export default defineConfig({ label: 'Testing', link: '/testing/', translations: { + ar: 'الاختبارات', fa: 'آزمایش کردن', es: 'Pruebas', ja: 'テスト', @@ -147,6 +155,7 @@ export default defineConfig({ label: 'Naming Conventions', link: '/naming-conventions/', translations: { + ar: 'اتفاقيات التسمية', fa: 'قراردادهای نامگذاری', es: 'Convenciones de Nomenclatura', ja: '命名規則', @@ -158,6 +167,7 @@ export default defineConfig({ label: 'Migration Guide', link: '/migration/', translations: { + ar: 'دليل الترقية', fa: 'راهنمای مهاجرت', es: 'Guía de Migración', ja: '移行ガイド', @@ -169,6 +179,7 @@ export default defineConfig({ label: 'FAQs', link: '/faqs/', translations: { + ar: 'الأسئلة الشائعة', fa: 'سوالات متداول', es: 'Preguntas Frecuentes', ja: 'よくある質問', @@ -181,27 +192,33 @@ export default defineConfig({ { label: 'Linter', badge: { text: 'new' }, - translations: { uk: 'Лінтер' }, + translations: { ar: 'المدقق', uk: 'Лінтер' }, items: [ { label: 'Overview ', link: '/lint/', - translations: { fa: 'بررسی اجمالی', ru: 'Обзор', uk: 'Огляд' }, + translations: { ar: 'نظرة عامة', fa: 'بررسی اجمالی', ru: 'Обзор', uk: 'Огляд' }, }, { label: 'Installation ', link: '/lint/installation/', - translations: { fa: 'نصب', ru: 'Установка', uk: 'Встановлення' }, + translations: { ar: 'التثبيت', fa: 'نصب', ru: 'Установка', uk: 'Встановлення' }, }, { label: 'Configuration ', link: '/lint/configuration/', - translations: { fa: 'پیکربندی', ru: 'Конфигурация', uk: 'Конфігурація' }, + translations: { + ar: 'الإعداد', + fa: 'پیکربندی', + ru: 'Конфигурация', + uk: 'Конфігурація', + }, }, { label: 'Customizing Rules ', link: '/lint/customizing-rules/', translations: { + ar: 'تخصيص القواعد', fa: 'سفارشی سازی قوانین', ru: 'Настройка правил', uk: 'Налаштування правил', @@ -210,13 +227,14 @@ export default defineConfig({ { label: 'Rules', autogenerate: { directory: '/lint-rules' }, - translations: { fa: 'قوانین', ru: 'Правила', uk: 'Правила' }, + translations: { ar: 'القواعد', fa: 'قوانین', ru: 'Правила', uk: 'Правила' }, }, ], }, { label: 'Tutorials', translations: { + ar: 'الدروس التعليمية', fa: 'آموزش ها', es: 'Tutoriales', ja: 'チュートリアル', @@ -228,6 +246,7 @@ export default defineConfig({ { label: 'Tools', translations: { + ar: 'الأدوات', fa: 'ابزار', es: 'Herramientas', ja: 'ツール', @@ -239,6 +258,7 @@ export default defineConfig({ label: 'IntelliJ Plugin', link: 'https://plugins.jetbrains.com/plugin/12129-bloc', translations: { + ar: 'إضافة IntelliJ', fa: 'پلاگین IntelliJ', es: 'Plugin de IntelliJ', ru: 'Плагин IntelliJ', @@ -249,6 +269,7 @@ export default defineConfig({ label: 'VSCode Extension', link: 'https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc', translations: { + ar: 'إضافة VS Code', fa: 'پلاگین VSCode', es: 'Extensión de VSCode', ru: 'Расширение VSCode', @@ -260,6 +281,7 @@ export default defineConfig({ { label: 'Reference', translations: { + ar: 'المرجع', fa: 'مرجع', es: 'Referencia', ja: 'APIリファレンス', diff --git a/docs/src/content/docs/ar/architecture.mdx b/docs/src/content/docs/ar/architecture.mdx index 5bfd9a5cf42..9eaf926acbd 100644 --- a/docs/src/content/docs/ar/architecture.mdx +++ b/docs/src/content/docs/ar/architecture.mdx @@ -14,7 +14,7 @@ import PresentationComponentSnippet from '~/components/architecture/Presentation ![Bloc Architecture](~/assets/concepts/bloc_architecture_full.png) -يتيح لنا استخدام مكتبة bloc فصل تطبيقنا إلى ثلاث طبقات: +يتيح لنا استخدام مكتبة bloc تقسيم التطبيق إلى ثلاث طبقات رئيسية: - طبقة العرض (Presentation) - طبقة منطق الأعمال (Business Logic) @@ -22,133 +22,128 @@ import PresentationComponentSnippet from '~/components/architecture/Presentation - المستودع (Repository) - مزود البيانات (Data Provider) -سنبدأ بالطبقة الأدنى (الأبعد عن واجهة المستخدم) ونتدرج صعودًا إلى طبقة العرض. +سنبدأ من الطبقة الأدنى (الأبعد عن واجهة المستخدم) ونتدرج صعودًا حتى نصل إلى طبقة +العرض. ## طبقة البيانات (Data Layer) -تتمثل مسؤولية طبقة البيانات في استرداد/معالجة البيانات من مصدر واحد أو أكثر. +تتمثل مسؤولية طبقة البيانات في جلب البيانات ومعالجتها من مصدر واحد أو أكثر. -يمكن تقسيم طبقة البيانات إلى جزأين: +يمكن تقسيم هذه الطبقة إلى جزأين: - المستودع (Repository) - مزود البيانات (Data Provider) -هذه الطبقة هي أدنى مستوى في التطبيق وتتفاعل مع قواعد البيانات، طلبات الشبكة، -ومصادر البيانات غير المتزامنة الأخرى. +تُعد هذه الطبقة الأدنى في التطبيق، وهي المسؤولة عن التفاعل مع قواعد البيانات، +وطلبات الشبكة، ومصادر البيانات غير المتزامنة الأخرى. ### مزود البيانات (Data Provider) -تتمثل مسؤولية مزود البيانات في توفير البيانات الخام. يجب أن يكون مزود البيانات -عامًا ومتعدد الاستخدامات. +تتمثل مهمة مزود البيانات في توفير البيانات الخام. يجب أن يكون عامًا ومرنًا +وقابلًا لإعادة الاستخدام. -عادةً ما يكشف مزود البيانات عن واجهات برمجية بسيطة (APIs) لأداء +عادةً ما يوفّر مزود البيانات واجهات برمجية بسيطة لتنفيذ [عمليات الإنشاء والقراءة والتحديث والحذف (CRUD)](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete). -قد يكون لدينا دوال مثل `createData` و `readData` و `updateData` و `deleteData` -كجزء من طبقة البيانات لدينا. +قد يتضمن ذلك دوال مثل `createData` و `readData` و `updateData` و `deleteData` +ضمن طبقة البيانات. ### المستودع (Repository) -طبقة المستودع هي غلاف حول مزود بيانات واحد أو أكثر تتواصل معه طبقة الـ Bloc -(منطق الأعمال). +تمثل طبقة المستودع غلافًا (wrapper) حول مزود بيانات واحد أو أكثر، وتتواصل معها +طبقة الـ Bloc. -كما ترى، يمكن لطبقة المستودع لدينا التفاعل مع مزودي بيانات متعددين وإجراء -تحويلات على البيانات قبل تسليم النتيجة إلى طبقة منطق الأعمال. +كما نلاحظ، يمكن للمستودع التعامل مع عدة مزودي بيانات وإجراء تحويلات على البيانات +قبل تمرير النتائج إلى طبقة منطق الأعمال. ## طبقة منطق الأعمال (Business Logic Layer) -تتمثل مسؤولية طبقة منطق الأعمال في الاستجابة للمدخلات من طبقة العرض بحالات -جديدة. يمكن أن تعتمد هذه الطبقة على مستودع واحد أو أكثر لاسترداد البيانات +تتمثل مسؤولية طبقة منطق الأعمال في الاستجابة لمدخلات طبقة العرض بإنتاج حالات +جديدة. يمكن أن تعتمد هذه الطبقة على مستودع واحد أو أكثر للحصول على البيانات اللازمة لبناء حالة التطبيق. -فكر في طبقة منطق الأعمال كجسر بين واجهة المستخدم (طبقة العرض) وطبقة البيانات. -يتم إخطار طبقة منطق الأعمال بالأحداث/الإجراءات من طبقة العرض، ثم تتواصل مع -المستودع من أجل بناء حالة جديدة لتستهلكها طبقة العرض. +يمكن اعتبار هذه الطبقة جسرًا بين واجهة المستخدم (طبقة العرض) وطبقة البيانات. فهي +تستقبل الأحداث أو الإجراءات من طبقة العرض، ثم تتواصل مع المستودع لبناء حالة +جديدة تستهلكها طبقة العرض. ### التواصل بين الـ Blocs (Bloc-to-Bloc Communication) -نظرًا لأن الـ blocs تكشف عن تدفقات (streams)، فقد يكون من المغري إنشاء bloc -يستمع إلى bloc آخر. يجب **ألا** تفعل ذلك. هناك بدائل أفضل من اللجوء إلى الكود -أدناه: +نظرًا لأن الـ blocs تعتمد على الـ streams، قد يكون من المغري إنشاء bloc يستمع +إلى bloc آخر. يجب **تجنب** هذا الأسلوب. توجد بدائل أفضل من اللجوء إلى الكود +التالي: -على الرغم من أن الكود أعلاه خالٍ من الأخطاء (بل ويقوم بالتنظيف الذاتي)، إلا أن -لديه مشكلة أكبر: إنه ينشئ تبعية بين اثنين من الـ blocs. +على الرغم من أن الكود أعلاه صحيح ويعالج الموارد بشكل مناسب، إلا أنه يخلق مشكلة +أكبر: وهي إنشاء تبعية مباشرة بين bloc وآخر. -بشكل عام، يجب تجنب التبعيات الشقيقة بين كيانين في نفس الطبقة المعمارية بأي ثمن، -لأنها تخلق اقترانًا محكمًا (tight-coupling) يصعب صيانته. نظرًا لأن الـ blocs تقع -في طبقة منطق الأعمال المعمارية، فلا ينبغي لأي bloc أن يعرف شيئًا عن أي bloc آخر. +بشكل عام، يجب تجنب التبعيات بين كيانات في نفس الطبقة المعمارية قدر الإمكان، لأن +ذلك يؤدي إلى اقتران محكم (tight coupling) يصعب صيانته. وبما أن الـ blocs تنتمي +إلى طبقة منطق الأعمال، فلا ينبغي لأي bloc أن يعرف عن bloc آخر. ![Application Architecture Layers](~/assets/architecture/architecture.png) -يجب أن يتلقى الـ bloc المعلومات فقط من خلال الأحداث ومن المستودعات المحقونة -(injected repositories) (أي المستودعات التي يتم تمريرها إلى الـ bloc في المُنشئ -الخاص به). +يجب أن يتلقى الـ bloc المعلومات فقط عبر الأحداث (events) أو من خلال المستودعات +التي يتم حقنها فيه عبر المُنشئ (constructor). -إذا كنت في موقف يحتاج فيه bloc إلى الاستجابة لـ bloc آخر، فلديك خياران آخران. -يمكنك دفع المشكلة إلى طبقة أعلى (في طبقة العرض)، أو إلى طبقة أدنى (في طبقة -النطاق/المجال). +إذا كنت بحاجة إلى أن يستجيب bloc لآخر، فهناك خياران أفضل: إما رفع الحل إلى طبقة +العرض، أو نقله إلى طبقة النطاق (Domain). -#### ربط الـ Blocs عبر طبقة العرض (Connecting Blocs through Presentation) +#### ربط الـ Blocs عبر طبقة العرض -يمكنك استخدام `BlocListener` للاستماع إلى bloc واحد وإضافة حدث إلى bloc آخر كلما -تغير الـ bloc الأول. +يمكن استخدام `BlocListener` للاستماع إلى bloc معين، وإضافة حدث إلى bloc آخر عند +تغيّر حالته. -يمنع الكود أعلاه `SecondBloc` من الحاجة إلى معرفة أي شيء عن `FirstBloc`، مما -يشجع على الاقتران المرن (loose-coupling). يستخدم تطبيق -[flutter_weather](/ar/tutorials/flutter-weather) +يمنع هذا الأسلوب `SecondBloc` من معرفة أي شيء عن `FirstBloc`، مما يعزز الاقتران +المرن (loose coupling). يستخدم تطبيق +[flutter_weather](/ar/tutorials/flutter-weather), [هذه التقنية](https://github.com/felangel/bloc/blob/b4c8db938ad71a6b60d4a641ec357905095c3965/examples/flutter_weather/lib/weather/view/weather_page.dart#L38-L42) -لتغيير سمة التطبيق بناءً على معلومات الطقس التي يتم تلقيها. +لتغيير سمة التطبيق بناءً على بيانات الطقس. -في بعض الحالات، قد لا ترغب في ربط اثنين من الـ blocs في طبقة العرض. بدلاً من -ذلك، قد يكون من المنطقي في كثير من الأحيان أن يتشارك الـ blocs في نفس مصدر -البيانات ويتم تحديثهما كلما تغيرت البيانات. +في بعض الحالات، قد لا يكون من المناسب الربط بين bloc وآخر في طبقة العرض. بدلاً +من ذلك، قد يكون من الأفضل أن يشتركا في نفس مصدر البيانات ويقوما بالتحديث عند +تغيّرها. -#### ربط الـ Blocs عبر طبقة النطاق (Connecting Blocs through Domain) +#### ربط الـ Blocs عبر طبقة النطاق (Domain) -يمكن لـ blocين الاستماع إلى تدفق (stream) من مستودع وتحديث حالاتهما بشكل مستقل -عن بعضهما البعض كلما تغيرت بيانات المستودع. يعد استخدام المستودعات التفاعلية -(reactive repositories) للحفاظ على مزامنة الحالة أمرًا شائعًا في تطبيقات -المؤسسات واسعة النطاق. +يمكن لبلوكين الاستماع إلى Stream من مستودع مشترك وتحديث حالاتهما بشكل مستقل عند +تغير البيانات. يُعد هذا النهج شائعًا في التطبيقات المؤسسية واسعة النطاق. -أولاً، قم بإنشاء أو استخدام مستودع يوفر `Stream` للبيانات. على سبيل المثال، يوفر -المستودع التالي تدفقًا لا نهائيًا لنفس الأفكار القليلة للتطبيق: +أولًا، أنشئ أو استخدم مستودعًا يوفر Stream للبيانات. على سبيل المثال، يوفر +المستودع التالي تدفقًا مستمرًا لبعض أفكار التطبيقات: -يمكن حقن نفس المستودع في كل bloc يحتاج إلى التفاعل مع أفكار التطبيق الجديدة. -أدناه يوجد `AppIdeaRankingBloc` الذي يصدر حالة لكل فكرة تطبيق واردة من المستودع -أعلاه: +يمكن حقن نفس المستودع في كل bloc يحتاج إلى الاستجابة لهذه البيانات. فيما يلي +`AppIdeaRankingBloc` الذي يُنتج حالة لكل فكرة جديدة واردة من المستودع: -لمزيد من المعلومات حول استخدام التدفقات (streams) مع Bloc، راجع -[كيفية استخدام Bloc مع التدفقات والتزامن](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency). +لمزيد من التفاصيل حول استخدام streams مع Bloc، راجع: +[كيفية استخدام Bloc مع الـ streams والتزامن](https://verygood.ventures/blog/how-to-use-bloc-with-streams-and-concurrency). ## طبقة العرض (Presentation Layer) -تتمثل مسؤولية طبقة العرض في تحديد كيفية عرض نفسها بناءً على حالة bloc واحدة أو -أكثر. بالإضافة إلى ذلك، يجب أن تتعامل مع مدخلات المستخدم وأحداث دورة حياة -التطبيق. +تتمثل مسؤولية طبقة العرض في تحديد كيفية عرض الواجهة بناءً على حالة bloc واحدة أو +أكثر، بالإضافة إلى التعامل مع مدخلات المستخدم وأحداث دورة حياة التطبيق. -تبدأ معظم تدفقات التطبيقات بحدث `AppStart` الذي يشغل التطبيق لجلب بعض البيانات +غالبًا ما تبدأ تدفقات التطبيق بحدث `AppStart` الذي يؤدي إلى جلب البيانات الأولية لعرضها للمستخدم. -في هذا السيناريو، ستقوم طبقة العرض بإضافة حدث `AppStart`. +في هذا السيناريو، تقوم طبقة العرض بإضافة حدث `AppStart`. -بالإضافة إلى ذلك، سيتعين على طبقة العرض تحديد ما يجب عرضه على الشاشة بناءً على -الحالة من طبقة الـ bloc. +كما يجب عليها تحديد ما سيتم عرضه على الشاشة بناءً على الحالة القادمة من طبقة الـ +bloc. -حتى الآن، على الرغم من أننا قدمنا بعض مقتطفات الكود، إلا أن كل هذا كان على مستوى -عالٍ إلى حد ما. في قسم الدروس التعليمية، سنقوم بجمع كل هذا معًا بينما نبني -العديد من تطبيقات الأمثلة المختلفة. +حتى الآن، وعلى الرغم من عرض بعض مقتطفات الكود، ما زال الشرح على مستوى مفاهيمي. +في قسم الدروس التعليمية، سنجمع كل هذه المفاهيم معًا أثناء بناء عدة تطبيقات +مثالية مختلفة. diff --git a/docs/src/content/docs/ar/bloc-concepts.mdx b/docs/src/content/docs/ar/bloc-concepts.mdx index ef2e5d819a0..2f05455239f 100644 --- a/docs/src/content/docs/ar/bloc-concepts.mdx +++ b/docs/src/content/docs/ar/bloc-concepts.mdx @@ -50,78 +50,74 @@ import DebounceEventTransformerSnippet from '~/components/concepts/bloc/Debounce :::note -يرجى التأكد من قراءة الأقسام التالية بعناية قبل العمل مع حزمة +يرجى التأكد من قراءة الأقسام التالية بعناية قبل العمل مع [`package:bloc`](https://pub.dev/packages/bloc). ::: -هناك العديد من المفاهيم الأساسية التي تعتبر بالغة الأهمية لفهم كيفية استخدام -حزمة bloc. +هناك عدة مفاهيم أساسية تُعد ضرورية لفهم كيفية استخدام حزمة bloc. -في الأقسام القادمة، سنناقش كل منها بالتفصيل بالإضافة إلى كيفية تطبيقها على تطبيق -عداد (counter app). +في الأقسام القادمة، سنناقش كل مفهوم بالتفصيل، ونستعرض كيف ينطبق ذلك على تطبيق +عدّاد (counter app). ## التدفقات (Streams) :::note -راجع [توثيق Dart الرسمي](https://dart.dev/tutorials/language/streams) لمزيد من -المعلومات حول الـ `Streams`. +اطّلع على [توثيق Dart الرسمي](https://dart.dev/tutorials/language/streams) لمزيد +من المعلومات حول `Streams`. ::: -التدفق (Stream) هو تسلسل من البيانات غير المتزامنة (asynchronous data). +التدفق (Stream) هو سلسلة من البيانات غير المتزامنة. -من أجل استخدام مكتبة bloc، من الضروري أن يكون لديك فهم أساسي للـ `Streams` -وكيفية عملها. +لاستخدام مكتبة bloc، من الضروري امتلاك فهم أساسي لـ `Streams` وكيف تعمل. -إذا لم تكن مألوفاً بالـ `Streams` ، فكر فقط في أنبوب يتدفق الماء من خلاله. -الأنبوب هو الـ `Stream` والماء هو البيانات غير المتزامنة. +إذا لم تكن مألوفًا بـ `Streams`، فتخيلها كأنبوب يجري فيه الماء: الأنبوب هو +`Stream` والماء هو البيانات غير المتزامنة. -يمكننا إنشاء `Stream` في Dart عن طريق كتابة دالة `async*` (مولد غير متزامن - -async generator). +يمكننا إنشاء `Stream` في Dart عبر كتابة دالة `async*` (مولّد غير متزامن - async +generator). -من خلال تمييز الدالة كـ `async*` ، يمكننا استخدام الكلمة المفتاحية `yield` -وإرجاع `Stream` من البيانات. في المثال أعلاه، نقوم بإرجاع `Stream` من الأعداد -الصحيحة حتى المعامل `max`. +عند تعليم الدالة بـ `async*` يصبح بإمكاننا استخدام الكلمة المفتاحية `yield` +وإرجاع `Stream` من البيانات. في المثال أعلاه، نُرجع `Stream` من أعداد صحيحة حتى +قيمة المُعامل `max`. -في كل مرة نستخدم فيها `yield` في دالة `async*` ، فإننا ندفع تلك القطعة من -البيانات عبر الـ `Stream`. +في كل مرة نستخدم فيها `yield` داخل دالة `async*`، فإننا ندفع تلك القيمة عبر الـ +`Stream`. -يمكننا استهلاك الـ `Stream` أعلاه بعدة طرق. إذا أردنا كتابة دالة لإرجاع مجموع -`Stream` من الأعداد الصحيحة، فقد تبدو كالتالي: +يمكننا استهلاك الـ `Stream` أعلاه بعدة طرق. إذا أردنا كتابة دالة تُرجع مجموع +`Stream` من الأعداد الصحيحة، فقد تكون كالتالي: -من خلال تمييز الدالة أعلاه كـ `async` ، يمكننا استخدام الكلمة المفتاحية `await` -وإرجاع `Future` من الأعداد الصحيحة. في هذا المثال، نقوم بانتظار كل قيمة في -التدفق وإرجاع مجموع جميع الأعداد الصحيحة في التدفق. +عند تعليم الدالة أعلاه بـ `async` يصبح بإمكاننا استخدام `await` وإرجاع `Future` +من الأعداد الصحيحة. في هذا المثال، ننتظر كل قيمة في التدفق ثم نُرجع مجموع جميع +الأعداد الموجودة فيه. -يمكننا تجميع كل ذلك معاً كالتالي: +يمكننا جمع كل ذلك معًا كالتالي: -الآن بعد أن أصبح لدينا فهم أساسي لكيفية عمل الـ `Streams` في Dart، نحن مستعدون -للتعرف على المكون الأساسي لحزمة bloc: الـ `Cubit`. +الآن بعد أن أصبح لدينا فهم أساسي لكيفية عمل `Streams` في Dart، نحن جاهزون للتعرف +على المكوّن الأساسي في حزمة bloc: `Cubit`. ## Cubit -الـ `Cubit` هو فئة (class) توسع `BlocBase` ويمكن توسيعها لإدارة أي نوع من -الحالات (state). +`Cubit` هو صنف (class) يوسّع `BlocBase` ويمكن توسيعه لإدارة أي نوع من الحالة. ![هيكلية Cubit](~/assets/concepts/cubit_architecture_full.png) -يمكن للـ `Cubit` كشف دوال يمكن استدعاؤها لتحفيز تغييرات الحالة. +يمكن لـ `Cubit` أن يوفّر دوالًا يمكن استدعاؤها لتحفيز تغييرات الحالة. -الحالات (States) هي مخرجات الـ `Cubit` وتمثل جزءاً من حالة تطبيقك. يمكن إخطار -مكونات واجهة المستخدم (UI) بالحالات وإعادة رسم أجزاء من نفسها بناءً على الحالة -الحالية. +الحالات (States) هي مخرجات `Cubit` وتمثل جزءًا من حالة تطبيقك. يمكن إبلاغ مكونات +واجهة المستخدم (UI) بالحالات وإعادة رسم أجزاء منها بناءً على الحالة الحالية. :::note -لمزيد من المعلومات حول أصول `Cubit` ، راجع +لمزيد من المعلومات حول أصل `Cubit`، راجع [المشكلة التالية](https://github.com/felangel/cubit/issues/69). ::: @@ -132,271 +128,265 @@ async generator). -عند إنشاء `Cubit` ، نحتاج إلى تحديد نوع الحالة التي سيديرها الـ `Cubit`. في حالة -`CounterCubit` أعلاه، يمكن تمثيل الحالة عبر `int` ، ولكن في الحالات الأكثر -تعقيداً قد يكون من الضروري استخدام `class` بدلاً من نوع بدائي (primitive type). +عند إنشاء `Cubit`، نحتاج إلى تحديد نوع الحالة التي سيديرها. في مثال +`CounterCubit` أعلاه، يمكن تمثيل الحالة باستخدام `int`، لكن في الحالات الأكثر +تعقيدًا قد نحتاج إلى استخدام `class` بدلًا من النوع البدائي (primitive type). -الشيء الثاني الذي نحتاج إلى القيام به عند إنشاء `Cubit` هو تحديد الحالة الأولية -(initial state). يمكننا القيام بذلك عن طريق استدعاء `super` مع قيمة الحالة -الأولية. في القطعة البرمجية أعلاه، نقوم بتعيين الحالة الأولية إلى `0` داخلياً، -ولكن يمكننا أيضاً جعل الـ `Cubit` أكثر مرونة من خلال قبول قيمة خارجية: +الخطوة الثانية عند إنشاء `Cubit` هي تحديد الحالة الابتدائية (initial state). +يمكننا فعل ذلك باستدعاء `super` وتمرير قيمة الحالة الابتدائية. في المثال أعلاه +نُعيّن الحالة الابتدائية إلى `0` داخليًا، لكن يمكننا جعل `Cubit` أكثر مرونة +بقبول قيمة خارجية: -سيسمح لنا هذا بإنشاء مثيلات من `CounterCubit` بحالات أولية مختلفة مثل: +وهذا يتيح لنا إنشاء مثيلات `CounterCubit` بحالات ابتدائية مختلفة مثل: ### تغييرات حالة Cubit -كل `Cubit` لديه القدرة على إخراج حالة جديدة عبر `emit`. +كل `Cubit` يمكنه إخراج حالة جديدة عبر `emit`. -في القطعة البرمجية أعلاه، يكشف `CounterCubit` عن طريقة عامة تسمى `increment` -والتي يمكن استدعاؤها خارجياً لإخطار الـ `CounterCubit` بزيادة حالته. عند استدعاء -`increment` ، يمكننا الوصول إلى الحالة الحالية للـ `Cubit` عبر أداة الحصول -`state` و `emit` حالة جديدة عن طريق إضافة 1 إلى الحالة الحالية. +في المثال أعلاه، يوفّر `CounterCubit` دالة عامة باسم `increment` يمكن استدعاؤها +خارجيًا لطلب زيادة الحالة. عند استدعاء `increment`، يمكننا الوصول إلى الحالة +الحالية عبر getter باسم `state` ثم `emit` حالة جديدة بإضافة 1 إلى الحالة +الحالية. :::caution -طريقة `emit` محمية (protected)، مما يعني أنه يجب استخدامها فقط داخل الـ `Cubit`. +الدالة `emit` محمية (protected)، ما يعني أنه يجب استخدامها فقط داخل `Cubit`. ::: ### استخدام Cubit -يمكننا الآن أخذ الـ `CounterCubit` الذي قمنا بتطبيقه واستخدامه! +يمكننا الآن أخذ `CounterCubit` الذي قمنا ببنائه واستخدامه! #### الاستخدام الأساسي -في القطعة البرمجية أعلاه، نبدأ بإنشاء مثيل من `CounterCubit`. ثم نقوم بطباعة -الحالة الحالية للـ cubit وهي الحالة الأولية (بما أنه لم يتم إرسال حالات جديدة -بعد). بعد ذلك، نستدعي دالة `increment` لتحفيز تغيير الحالة. أخيراً، نطبع حالة -الـ `Cubit` مرة أخرى والتي انتقلت من `0` إلى `1` ونستدعي `close` على الـ `Cubit` -لإغلاق تدفق الحالة الداخلي. +في المثال أعلاه، نبدأ بإنشاء مثيل من `CounterCubit`. ثم نطبع الحالة الحالية وهي +الحالة الابتدائية (لأنه لم يتم إصدار حالات جديدة بعد). بعد ذلك، نستدعي +`increment` لتحفيز تغيير الحالة. أخيرًا، نطبع حالة `Cubit` مرة أخرى وقد انتقلت +من `0` إلى `1`، ثم نستدعي `close` لإغلاق تدفق الحالة الداخلي. #### استخدام التدفق (Stream Usage) -يكشف الـ `Cubit` عن `Stream` يسمح لنا بتلقي تحديثات الحالة في الوقت الفعلي: +يوفّر `Cubit` تدفقًا (`Stream`) يتيح لنا استقبال تحديثات الحالة في الوقت الفعلي: -في القطعة البرمجية أعلاه، نقوم بالاشتراك في `CounterCubit` واستدعاء print عند كل -تغيير للحالة. ثم نقوم باستدعاء دالة `increment` التي ستطلق حالة جديدة. أخيراً، -نقوم باستدعاء `cancel` على الـ `subscription` عندما لا نعد نرغب في تلقي -التحديثات ونغلق الـ `Cubit`. +في المثال أعلاه، نشترك في `CounterCubit` ونطبع عند كل تغيير للحالة. ثم نستدعي +`increment` لإصدار حالة جديدة. أخيرًا، نستدعي `cancel` على `subscription` عندما +لا نعود بحاجة للتحديثات، ونغلق `Cubit`. :::note تمت إضافة `await Future.delayed(Duration.zero)` في هذا المثال لتجنب إلغاء -الاشتراك فوراً. +الاشتراك فورًا. ::: :::caution -سيتم استقبال تغييرات الحالة اللاحقة فقط عند استدعاء `listen` على الـ `Cubit`. +عند استدعاء `listen` على `Cubit` سيتم استقبال تغييرات الحالة اللاحقة فقط. ::: ### مراقبة Cubit -عندما يرسل الـ `Cubit` حالة جديدة، يحدث "تغيير" (`Change`). يمكننا مراقبة جميع -التغييرات لـ `Cubit` معين عن طريق تجاوز `onChange`. +عندما يرسل `Cubit` حالة جديدة، يحدث `Change`. يمكننا مراقبة جميع `Changes` لـ +`Cubit` معين عبر تجاوز `onChange`. -يمكننا بعد ذلك التفاعل مع الـ `Cubit` ومراقبة جميع التغييرات المطبوعة على وحدة +يمكننا بعد ذلك التفاعل مع `Cubit` ومشاهدة جميع التغييرات المطبوعة على وحدة التحكم (console). -المثال أعلاه سيخرج: +سيكون الإخراج في المثال أعلاه: :::note -يحدث الـ `Change` قبل تحديث حالة الـ `Cubit` مباشرة. يتكون الـ `Change` من -الحالة الحالية (`currentState`) والحالة التالية (`nextState`). +يحدث `Change` قبل تحديث حالة `Cubit` مباشرة. ويتكون من `currentState` و +`nextState`. ::: #### BlocObserver -إحدى المزايا الإضافية لاستخدام مكتبة bloc هي أنه يمكننا الوصول إلى جميع -التغييرات (`Changes`) في مكان واحد. على الرغم من أننا في هذا التطبيق لدينا -`Cubit` واحد فقط، إلا أنه من الشائع جداً في التطبيقات الكبيرة وجود العديد من الـ -`Cubits` التي تدير أجزاء مختلفة من حالة التطبيق. +من مزايا استخدام مكتبة bloc أننا نستطيع الوصول إلى جميع `Changes` في مكان واحد. +رغم أن هذا التطبيق يحتوي على `Cubit` واحد فقط، إلا أنه شائع في التطبيقات الكبيرة +وجود عدة `Cubits` تدير أجزاء مختلفة من حالة التطبيق. -إذا أردنا أن نكون قادرين على القيام بشيء ما استجابةً لجميع التغييرات، يمكننا -ببساطة إنشاء `BlocObserver` الخاص بنا. +إذا أردنا تنفيذ إجراء استجابةً لجميع `Changes`، يمكننا ببساطة إنشاء +`BlocObserver` خاص بنا. :::note -كل ما نحتاجه هو توسيع `BlocObserver` وتجاوز طريقة `onChange`. +كل ما نحتاجه هو توسيع `BlocObserver` وتجاوز `onChange`. ::: -من أجل استخدام `SimpleBlocObserver` ، نحتاج فقط إلى تعديل دالة `main`: +لاستخدام `SimpleBlocObserver` نحتاج فقط إلى تعديل الدالة `main`: -القطعة البرمجية أعلاه ستخرج بعد ذلك: +وسيكون الإخراج: :::note -يتم استدعاء تجاوز `onChange` الداخلي أولاً، والذي يستدعي `super.onChange` لإخطار -`onChange` في الـ `BlocObserver`. +يُستدعى تجاوز `onChange` الداخلي أولًا، ثم يستدعي `super.onChange` لإشعار +`onChange` في `BlocObserver`. ::: :::tip -في `BlocObserver` ، لدينا إمكانية الوصول إلى مثيل الـ `Cubit` بالإضافة إلى الـ -`Change` نفسه. +في `BlocObserver` لدينا إمكانية الوصول إلى مثيل `Cubit` بالإضافة إلى `Change` +نفسه. ::: -### معالجة الأخطاء في Cubit +### معالجة أخطاء Cubit -كل `Cubit` لديه طريقة `addError` والتي يمكن استخدامها للإشارة إلى حدوث خطأ. +يمتلك كل `Cubit` دالة `addError` يمكن استخدامها للإشارة إلى حدوث خطأ. :::note -يمكن تجاوز `onError` داخل الـ `Cubit` للتعامل مع جميع الأخطاء لـ `Cubit` معين. +يمكن تجاوز `onError` داخل `Cubit` لمعالجة جميع الأخطاء الخاصة بـ `Cubit` محدد. ::: -يمكن أيضاً تجاوز `onError` في `BlocObserver` للتعامل مع جميع الأخطاء المبلغ عنها -عالمياً. +يمكن أيضًا تجاوز `onError` في `BlocObserver` لمعالجة جميع الأخطاء المُبلّغ عنها +بشكل عام. -إذا قمنا بتشغيل نفس البرنامج مرة أخرى، فسنرى المخرجات التالية: +إذا شغّلنا نفس البرنامج مرة أخرى، ينبغي أن نرى الإخراج التالي: ## Bloc -الـ `Bloc` هو فئة أكثر تقدماً تعتمد على الأحداث (`events`) لتحفيز تغييرات الحالة -(`state`) بدلاً من الدوال. يوسع الـ `Bloc` أيضاً `BlocBase` ، مما يعني أن لديه -واجهة برمجة تطبيقات عامة مشابهة لـ `Cubit`. ومع ذلك، بدلاً من استدعاء "دالة" على -الـ `Bloc` وإرسال حالة جديدة مباشرة، تستقبل الـ `Blocs` "أحداثاً" وتحول الأحداث -الواردة إلى حالات صادرة. +`Bloc` هو صنف أكثر تقدمًا يعتمد على `events` لتحفيز تغييرات `state` بدلًا من +الدوال. كما أنه يوسّع `BlocBase`، ما يعني أن لديه واجهة عامة مشابهة لـ `Cubit`. +لكن بدلًا من استدعاء دالة على `Bloc` وإصدار `state` جديد مباشرة، تستقبل `Blocs` +أحداثًا (`events`) وتحوّل الأحداث الواردة إلى حالات صادرة (`states`). ![هيكلية Bloc](~/assets/concepts/bloc_architecture_full.png) ### إنشاء Bloc -إنشاء `Bloc` مشابه لإنشاء `Cubit` ، إلا أنه بالإضافة إلى تحديد الحالة التي -سنديرها، يجب علينا أيضاً تحديد الحدث الذي سيكون الـ `Bloc` قادراً على معالجته. +إنشاء `Bloc` يشبه إنشاء `Cubit`، لكن بالإضافة إلى تحديد الحالة التي سنديرها، يجب +أيضًا تحديد الحدث الذي سيتمكن `Bloc` من معالجته. -الأحداث هي المدخلات للـ Bloc. عادة ما يتم إضافتها استجابةً لتفاعلات المستخدم مثل -ضغطات الأزرار أو أحداث دورة الحياة مثل تحميل الصفحة. +الأحداث هي مدخلات Bloc. عادة تُضاف استجابةً لتفاعلات المستخدم مثل ضغط الأزرار أو +لأحداث دورة الحياة مثل تحميل الصفحة. -تماماً كما هو الحال عند إنشاء `CounterCubit` ، يجب علينا تحديد حالة أولية عن -طريق تمريرها إلى الفئة العليا عبر `super`. +وكما في `CounterCubit`، يجب تحديد الحالة الابتدائية بتمريرها إلى الصنف الأب عبر +`super`. ### تغييرات حالة Bloc -يتطلب منا الـ `Bloc` تسجيل معالجات الأحداث (event handlers) عبر API الـ -`on` ، على عكس الدوال في `Cubit`. معالج الأحداث مسؤول عن تحويل أي أحداث -واردة إلى صفر أو أكثر من الحالات الصادرة. +يتطلب `Bloc` تسجيل معالجات الأحداث عبر واجهة `on`، بخلاف الدوال في +`Cubit`. معالج الحدث مسؤول عن تحويل أي أحداث واردة إلى صفر أو أكثر من الحالات +الصادرة. :::tip -يتمتع معالج الأحداث (`EventHandler`) بإمكانية الوصول إلى الحدث المضاف بالإضافة -إلى "الباعث" (`Emitter`) الذي يمكن استخدامه لإرسال صفر أو أكثر من الحالات -استجابةً للحدث الوارد. +يمتلك `EventHandler` إمكانية الوصول إلى الحدث المضاف، إضافةً إلى `Emitter` الذي +يمكن استخدامه لإصدار صفر أو أكثر من الحالات استجابةً للحدث الوارد. ::: -يمكننا بعد ذلك تحديث الـ `EventHandler` لمعالجة حدث `CounterIncrementPressed`: +يمكننا بعد ذلك تحديث `EventHandler` لمعالجة حدث `CounterIncrementPressed`: -في القطعة البرمجية أعلاه، قمنا بتسجيل `EventHandler` لإدارة جميع أحداث -`CounterIncrementPressed`. لكل حدث `CounterIncrementPressed` وارد، يمكننا الوصول -إلى الحالة الحالية للـ bloc عبر أداة الحصول `state` و `emit(state + 1)`. +في المثال أعلاه، سجّلنا `EventHandler` لإدارة جميع أحداث +`CounterIncrementPressed`. ولكل حدث وارد، يمكننا الوصول إلى الحالة الحالية عبر +getter باسم `state` ثم استدعاء `emit(state + 1)`. :::note -بما أن فئة `Bloc` توسع `BlocBase` ، فلدينا إمكانية الوصول إلى الحالة الحالية للـ -bloc في أي وقت عبر أداة الحصول `state` تماماً كما هو الحال في `Cubit`. +بما أن `Bloc` يوسّع `BlocBase`، فيمكننا الوصول إلى الحالة الحالية في أي وقت عبر +`state` تمامًا كما في `Cubit`. ::: :::caution -لا ينبغي أبداً للـ Blocs إرسال (`emit`) حالات جديدة مباشرة. بدلاً من ذلك، يجب أن -يتم إخراج كل تغيير في الحالة استجابةً لحدث وارد داخل `EventHandler`. +لا ينبغي على `Blocs` إصدار حالات جديدة مباشرة عبر `emit`. بدلًا من ذلك، يجب أن +ينتج كل تغيير في الحالة استجابةً لحدث وارد داخل `EventHandler`. ::: :::caution -سيتجاهل كل من الـ blocs والـ cubits الحالات المكررة. إذا قمنا بإرسال -`State nextState` حيث `state == nextState` ، فلن يحدث أي تغيير في الحالة. +يتجاهل كل من blocs و cubits الحالات المكررة. إذا قمنا بإصدار `State nextState` +بحيث `state == nextState`، فلن يحدث أي تغيير في الحالة. ::: ### استخدام Bloc -في هذه المرحلة، يمكننا إنشاء مثيل من `CounterBloc` الخاص بنا واستخدامه! +في هذه المرحلة، يمكننا إنشاء مثيل من `CounterBloc` واستخدامه! #### الاستخدام الأساسي -في القطعة البرمجية أعلاه، نبدأ بإنشاء مثيل من `CounterBloc`. ثم نقوم بطباعة -الحالة الحالية للـ `Bloc` وهي الحالة الأولية (بما أنه لم يتم إرسال حالات جديدة -بعد). بعد ذلك، نضيف حدث `CounterIncrementPressed` لتحفيز تغيير الحالة. أخيراً، -نطبع حالة الـ `Bloc` مرة أخرى والتي انتقلت من `0` إلى `1` ونستدعي `close` على -الـ `Bloc` لإغلاق تدفق الحالة الداخلي. +في المثال أعلاه، نبدأ بإنشاء مثيل من `CounterBloc`. ثم نطبع الحالة الحالية وهي +الحالة الابتدائية (لأنه لم يتم إصدار حالات جديدة بعد). بعد ذلك، نضيف حدث +`CounterIncrementPressed` لتحفيز تغيير الحالة. أخيرًا، نطبع حالة `Bloc` مرة أخرى +وقد انتقلت من `0` إلى `1`، ثم نستدعي `close` لإغلاق تدفق الحالة الداخلي. :::note -تمت إضافة `await Future.delayed(Duration.zero)` لضمان انتظار دورة حلقة الأحداث -(event-loop) التالية (مما يسمح لـ `EventHandler` بمعالجة الحدث). +تمت إضافة `await Future.delayed(Duration.zero)` لضمان انتظار الدورة التالية من +event-loop (ما يسمح لـ `EventHandler` بمعالجة الحدث). ::: #### استخدام التدفق (Stream Usage) -تماماً كما هو الحال مع `Cubit` ، فإن الـ `Bloc` هو نوع خاص من الـ `Stream` ، مما -يعني أنه يمكننا أيضاً الاشتراك في الـ `Bloc` للحصول على تحديثات فورية لحالته: +كما هو الحال مع `Cubit`، فإن `Bloc` نوع خاص من `Stream`، ما يعني أنه يمكننا +أيضًا الاشتراك في `Bloc` للحصول على تحديثات فورية لحالته: -في القطعة البرمجية أعلاه، نقوم بالاشتراك في `CounterBloc` واستدعاء print عند كل -تغيير للحالة. ثم نقوم بإضافة حدث `CounterIncrementPressed` الذي يحفز معالج -الأحداث `on` ويرسل حالة جديدة. أخيراً، نقوم باستدعاء -`cancel` على الاشتراك عندما لا نعد نرغب في تلقي التحديثات ونغلق الـ `Bloc`. +في المثال أعلاه، نشترك في `CounterBloc` ونطبع عند كل تغيير للحالة. ثم نضيف حدث +`CounterIncrementPressed` الذي يفعّل `EventHandler` الخاص بـ +`on` ويُصدر حالة جديدة. أخيرًا، نستدعي `cancel` على +الاشتراك عندما لا نعود بحاجة للتحديثات، ونغلق `Bloc`. :::note تمت إضافة `await Future.delayed(Duration.zero)` في هذا المثال لتجنب إلغاء -الاشتراك فوراً. +الاشتراك فورًا. ::: ### مراقبة Bloc -بما أن `Bloc` يوسع `BlocBase` ، يمكننا مراقبة جميع تغييرات الحالة للـ `Bloc` +بما أن `Bloc` يوسّع `BlocBase`، يمكننا مراقبة جميع تغييرات الحالة لـ `Bloc` باستخدام `onChange`. @@ -405,120 +395,117 @@ bloc في أي وقت عبر أداة الحصول `state` تماماً كما -الآن إذا قمنا بتشغيل القطعة البرمجية أعلاه، فستكون المخرجات: +الآن إذا شغّلنا المثال أعلاه، سيكون الإخراج: -أحد العوامل المميزة الرئيسية بين `Bloc` و `Cubit` هو أنه نظراً لأن `Bloc` يعتمد -على الأحداث، فنحن قادرون أيضاً على التقاط معلومات حول ما حفز تغيير الحالة. +أحد الفروق الجوهرية بين `Bloc` و `Cubit` هو أنه بما أن `Bloc` يعتمد على الأحداث، +يمكننا أيضًا التقاط معلومات حول ما الذي حفّز تغيير الحالة. -يمكننا القيام بذلك عن طريق تجاوز `onTransition`. +يمكننا فعل ذلك بتجاوز `onTransition`. -يسمى الانتقال من حالة إلى أخرى "انتقالاً" (`Transition`). يتكون الـ `Transition` -من الحالة الحالية، والحدث، والحالة التالية. +الانتقال من حالة إلى أخرى يُسمى `Transition`. ويتكون `Transition` من الحالة +الحالية والحدث والحالة التالية. -إذا قمنا بعد ذلك بإعادة تشغيل نفس قطعة `main.dart` من قبل، فسنرى المخرجات -التالية: +إذا أعدنا تشغيل نفس مثال `main.dart` السابق، ينبغي أن نرى الإخراج التالي: :::note -يتم استدعاء `onTransition` قبل `onChange` ويحتوي على الحدث الذي حفز التغيير من -الحالة الحالية (`currentState`) إلى الحالة التالية (`nextState`). +يُستدعى `onTransition` قبل `onChange` ويحتوي على الحدث الذي حفّز التغيير من +`currentState` إلى `nextState`. ::: #### BlocObserver -تماماً كما كان من قبل، يمكننا تجاوز `onTransition` في `BlocObserver` مخصص -لمراقبة جميع الانتقالات التي تحدث من مكان واحد. +كما في السابق، يمكننا تجاوز `onTransition` في `BlocObserver` مخصص لمراقبة جميع +الانتقالات من مكان واحد. -يمكننا تهيئة `SimpleBlocObserver` تماماً كما فعلنا سابقاً: +يمكننا تهيئة `SimpleBlocObserver` تمامًا كما فعلنا سابقًا: -الآن إذا قمنا بتشغيل القطعة البرمجية أعلاه، يجب أن تبدو المخرجات كالتالي: +الآن إذا شغّلنا المثال أعلاه، يجب أن يكون الإخراج كالتالي: :::note -يتم استدعاء `onTransition` أولاً (المحلي قبل العالمي) يليه `onChange`. +يُستدعى `onTransition` أولًا (المحلي قبل العام) ثم يليه `onChange`. ::: -ميزة فريدة أخرى لمثيلات `Bloc` هي أنها تسمح لنا بتجاوز `onEvent` والتي يتم -استدعاؤها كلما تمت إضافة حدث جديد إلى الـ `Bloc`. تماماً كما هو الحال مع -`onChange` و `onTransition` ، يمكن تجاوز `onEvent` محلياً وعالمياً. +ميزة أخرى فريدة في `Bloc` هي إمكانية تجاوز `onEvent`، والتي تُستدعى كلما تمت +إضافة حدث جديد إلى `Bloc`. وكما في `onChange` و `onTransition`، يمكن تجاوز +`onEvent` محليًا وعالميًا. -يمكننا تشغيل نفس `main.dart` كما كان من قبل وسنرى المخرجات التالية: +يمكننا تشغيل نفس `main.dart` كما في السابق، وينبغي أن نرى الإخراج التالي: :::note -يتم استدعاء `onEvent` بمجرد إضافة الحدث. يتم استدعاء `onEvent` المحلي قبل -`onEvent` العالمي في `BlocObserver`. +يتم استدعاء `onEvent` بمجرد إضافة الحدث. ويُستدعى `onEvent` المحلي قبل `onEvent` +العام داخل `BlocObserver`. ::: -### معالجة الأخطاء في Bloc +### معالجة أخطاء Bloc -تماماً كما هو الحال مع `Cubit` ، فإن كل `Bloc` لديه طريقة `addError` و -`onError`. يمكننا الإشارة إلى حدوث خطأ عن طريق استدعاء `addError` من أي مكان -داخل الـ `Bloc`. يمكننا بعد ذلك التفاعل مع جميع الأخطاء عن طريق تجاوز `onError` -تماماً كما هو الحال مع `Cubit`. +كما هو الحال مع `Cubit`، يمتلك كل `Bloc` دالتي `addError` و `onError`. يمكننا +الإشارة إلى حدوث خطأ عبر استدعاء `addError` من أي مكان داخل `Bloc`. ثم يمكننا +الاستجابة لجميع الأخطاء بتجاوز `onError` تمامًا كما في `Cubit`. -إذا أعدنا تشغيل نفس `main.dart` كما كان من قبل، يمكننا أن نرى كيف يبدو الأمر عند -الإبلاغ عن خطأ: +إذا أعدنا تشغيل نفس `main.dart` كما في السابق، يمكننا رؤية شكل الإبلاغ عن الخطأ: :::note -يتم استدعاء `onError` المحلي أولاً يليه `onError` العالمي في `BlocObserver`. +يُستدعى `onError` المحلي أولًا ثم يليه `onError` العام داخل `BlocObserver`. ::: :::note -تعمل `onError` و `onChange` بنفس الطريقة تماماً لكل من مثيلات `Bloc` و `Cubit`. +تعمل `onError` و `onChange` بالطريقة نفسها تمامًا لكل من `Bloc` و `Cubit`. ::: :::caution -يتم أيضاً الإبلاغ عن أي استثناءات غير معالجة تحدث داخل `EventHandler` إلى +أي استثناءات غير معالجة تحدث داخل `EventHandler` يتم الإبلاغ عنها أيضًا إلى `onError`. ::: ## Cubit مقابل Bloc -الآن بعد أن غطينا أساسيات فئتي `Cubit` و `Bloc` ، قد تتساءل متى يجب عليك استخدام -`Cubit` ومتى يجب عليك استخدام `Bloc`. +الآن بعد أن غطّينا أساسيات `Cubit` و `Bloc`، قد تتساءل متى تستخدم `Cubit` ومتى +تستخدم `Bloc`. ### مزايا Cubit #### البساطة -واحدة من أكبر مزايا استخدام `Cubit` هي البساطة. عند إنشاء `Cubit` ، علينا فقط -تحديد الحالة بالإضافة إلى الدوال التي نريد كشفها لتغيير الحالة. بالمقارنة، عند -إنشاء `Bloc` ، يتعين علينا تحديد الحالات، والأحداث، وتطبيق الـ `EventHandler`. -هذا يجعل `Cubit` أسهل في الفهم ويتطلب كوداً أقل. +من أكبر مزايا `Cubit` هي البساطة. عند إنشاء `Cubit` نحتاج فقط لتحديد الحالة +والدوال التي نريد توفيرها لتغيير الحالة. بالمقارنة، عند إنشاء `Bloc` نحتاج +لتحديد الحالات والأحداث وتطبيق `EventHandler`. هذا يجعل `Cubit` أسهل في الفهم مع +كود أقل. -الآن دعونا نلقي نظرة على تطبيقي العداد: +لنلقِ نظرة الآن على تنفيذي العداد: ##### CounterCubit @@ -528,46 +515,42 @@ bloc في أي وقت عبر أداة الحصول `state` تماماً كما -تطبيق `Cubit` أكثر إيجازاً وبدلاً من تحديد الأحداث بشكل منفصل، تعمل الدوال مثل -الأحداث. بالإضافة إلى ذلك، عند استخدام `Cubit` ، يمكننا ببساطة استدعاء `emit` من -أي مكان لتحفيز تغيير الحالة. +تنفيذ `Cubit` أكثر اختصارًا، وبدلًا من تعريف الأحداث بشكل منفصل، تقوم الدوال +بدور الأحداث. إضافةً إلى ذلك، عند استخدام `Cubit` يمكننا ببساطة استدعاء `emit` +من أي مكان لتحفيز تغيير الحالة. ### مزايا Bloc #### قابلية التتبع (Traceability) -واحدة من أكبر مزايا استخدام `Bloc` هي معرفة تسلسل تغييرات الحالة بالإضافة إلى ما -حفز تلك التغييرات بالضبط. بالنسبة للحالة التي تعتبر بالغة الأهمية لوظائف -التطبيق، قد يكون من المفيد جداً استخدام نهج يعتمد أكثر على الأحداث من أجل التقاط -جميع الأحداث بالإضافة إلى تغييرات الحالة. +من أكبر مزايا `Bloc` معرفة تسلسل تغييرات الحالة، ومعرفة ما الذي حفّز تلك +التغييرات بدقة. عندما تكون الحالة حساسة أو حاسمة لوظائف التطبيق، قد يكون من +المفيد استخدام نهج قائم على الأحداث لالتقاط الأحداث إضافةً إلى تغييرات الحالة. -حالة استخدام شائعة قد تكون إدارة حالة المصادقة (`AuthenticationState`). للتبسيط، -لنقل أنه يمكننا تمثيل `AuthenticationState` عبر `enum`: +حالة استخدام شائعة هي إدارة `AuthenticationState`. وللتبسيط، لنفترض أننا نمثل +`AuthenticationState` عبر `enum`: -قد تكون هناك أسباب عديدة لتغير حالة التطبيق من `authenticated` (مُصادق عليه) إلى -`unauthenticated` (غير مُصادق عليه). على سبيل المثال، قد يكون المستخدم قد نقر -على زر تسجيل الخروج وطلب تسجيل الخروج من التطبيق. من ناحية أخرى، ربما تم إلغاء -رمز وصول المستخدم وتم تسجيل خروجه قسراً. عند استخدام `Bloc` ، يمكننا تتبع كيفية -وصول حالة التطبيق إلى حالة معينة بوضوح. +قد توجد أسباب عديدة لتغير حالة التطبيق من `authenticated` إلى `unauthenticated`. +على سبيل المثال، قد يضغط المستخدم زر تسجيل الخروج ويطلب تسجيل خروجه. أو ربما تم +إلغاء رمز وصول المستخدم وتم تسجيل خروجه إجباريًا. عند استخدام `Bloc` يمكننا +تتبّع كيف وصلت حالة التطبيق إلى حالة معينة بوضوح. -يعطينا الـ `Transition` أعلاه كل المعلومات التي نحتاجها لفهم سبب تغير الحالة. -إذا كنا قد استخدمنا `Cubit` لإدارة `AuthenticationState` ، فستبدو سجلاتنا -كالتالي: +يوفر لنا `Transition` أعلاه كل المعلومات اللازمة لفهم سبب تغير الحالة. ولو +استخدمنا `Cubit` لإدارة `AuthenticationState` فستبدو السجلات كالتالي: -يخبرنا هذا أن المستخدم قد سجل خروجه ولكنه لا يشرح السبب، وهو ما قد يكون حاسماً -لتصحيح الأخطاء وفهم كيفية تغير حالة التطبيق بمرور الوقت. +هذا يخبرنا أن المستخدم خرج من النظام، لكنه لا يوضح السبب، وقد يكون ذلك مهمًا +لتصحيح الأخطاء وفهم كيفية تغيّر حالة التطبيق مع الزمن. #### تحويلات الأحداث المتقدمة -مجال آخر يتفوق فيه `Bloc` على `Cubit` هو عندما نحتاج إلى الاستفادة من العوامل -التفاعلية (reactive operators) مثل `buffer` و `debounceTime` و `throttle` وما -إلى ذلك. +مجال آخر يتفوق فيه `Bloc` على `Cubit` هو عندما نحتاج للاستفادة من العوامل +التفاعلية مثل `buffer` و `debounceTime` و `throttle` وغيرها. :::tip @@ -577,28 +560,28 @@ transformers). ::: -يحتوي الـ `Bloc` على "مصب أحداث" (event sink) يسمح لنا بالتحكم في تدفق الأحداث -الواردة وتحويلها. +يمتلك `Bloc` قناة إدخال للأحداث (event sink) تتيح لنا التحكم في تدفق الأحداث +الواردة وتحويله. -على سبيل المثال، إذا كنا نبني بحثاً في الوقت الفعلي، فسنرغب على الأرجح في -استخدام "تخامد" (debounce) للطلبات المرسلة إلى الخلفية (backend) من أجل تجنب -تجاوز حدود معدل الطلبات (rate-limiting) بالإضافة إلى تقليل التكلفة/الحمل على -الخلفية. +على سبيل المثال، إذا كنا نبني بحثًا لحظيًا، فمن المرجح أننا سنحتاج إلى تطبيق +`debounce` على طلبات الخلفية لتجنب rate-limiting، وكذلك لتقليل التكلفة/الضغط على +الخادم. -باستخدام `Bloc` ، يمكننا توفير `EventTransformer` مخصص لتغيير الطريقة التي يعالج -بها الـ `Bloc` الأحداث الواردة. +باستخدام `Bloc`، يمكننا توفير `EventTransformer` مخصص لتغيير طريقة معالجة +الأحداث الواردة. -باستخدام الكود أعلاه، يمكننا بسهولة تطبيق التخامد (debounce) على الأحداث الواردة -بقليل جداً من الكود الإضافي. +باستخدام الكود أعلاه، يمكننا بسهولة تطبيق `debounce` على الأحداث الواردة مع +مقدار بسيط جدًا من الكود الإضافي. :::tip -تحقق من [`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) -للحصول على مجموعة مختارة من محولات الأحداث. +اطّلع على +[`package:bloc_concurrency`](https://pub.dev/packages/bloc_concurrency) للحصول +على مجموعة مُوجهة من محولات الأحداث. ::: -إذا كنت غير متأكد من أيهما تستخدم، ابدأ بـ `Cubit` ويمكنك لاحقاً إعادة هيكلة -الكود أو الترقية إلى `Bloc` حسب الحاجة. +إذا لم تكن متأكدًا مما يجب استخدامه، ابدأ بـ `Cubit` ويمكنك لاحقًا إعادة الهيكلة +أو التوسّع إلى `Bloc` عند الحاجة. diff --git a/docs/src/content/docs/ar/faqs.mdx b/docs/src/content/docs/ar/faqs.mdx index fc8c549d38a..79b1bb0e1a6 100644 --- a/docs/src/content/docs/ar/faqs.mdx +++ b/docs/src/content/docs/ar/faqs.mdx @@ -23,11 +23,11 @@ import BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSni ## عدم تحديث الحالة (State Not Updating) -❔ **سؤال**: أقوم بإصدار حالة (emitting a state) في الـ bloc الخاص بي ولكن واجهة -المستخدم (UI) لا تتحدث. ما الخطأ الذي أرتكبه؟ +❔ **سؤال**: أقوم بإصدار حالة داخل الـ bloc، لكن واجهة المستخدم لا تتحدّث. ما +الخطأ؟ -💡 **إجابة**: إذا كنت تستخدم `Equatable`، فتأكد من تمرير جميع الخصائص إلى مُحصّل -الخصائص (`props` getter). +💡 **إجابة**: إذا كنت تستخدم `Equatable`، فتأكّد من تضمين جميع الخصائص داخل +`props`. ✅ **صحيح (GOOD)** @@ -39,8 +39,8 @@ import BlocExternalForEachSnippet from '~/components/faqs/BlocExternalForEachSni -بالإضافة إلى ذلك، تأكد من أنك تصدر مثيلاً جديدًا للحالة (new instance of the -state) في الـ bloc الخاص بك. +كذلك تأكّد من إصدار نسخة جديدة من الحالة داخل الـ bloc، وليس إعادة استخدام نفس +الكائن. ✅ **صحيح (GOOD)** @@ -54,50 +54,47 @@ state) في الـ bloc الخاص بك. :::caution -يجب دائمًا نسخ خصائص `Equatable` بدلاً من تعديلها. إذا كانت فئة `Equatable` -تحتوي على `List` أو `Map` كخصائص، فتأكد من استخدام `List.of` أو `Map.of` على -التوالي لضمان تقييم المساواة بناءً على قيم الخصائص بدلاً من المرجع (reference). +يجب دائمًا نسخ الخصائص في الكائنات التي تستخدم `Equatable` بدل تعديلها مباشرة. +وإذا كانت الفئة تحتوي على `List` أو `Map`، فاستخدم `List.of` أو `Map.of` حتى +تُحتسب المساواة بناءً على القيم لا على مرجع الكائن. ::: ## متى يجب استخدام Equatable -❔**سؤال**: متى يجب علي استخدام Equatable؟ +❔ **سؤال**: متى أستخدم Equatable؟ -💡**إجابة**: +💡 **إجابة**: -في السيناريو أعلاه، إذا كانت `StateA` ترث من `Equatable`، فسيحدث تغيير واحد فقط -في الحالة (سيتم تجاهل الإصدار الثاني). بشكل عام، يجب عليك استخدام `Equatable` -إذا كنت ترغب في تحسين الكود الخاص بك لتقليل عدد عمليات إعادة البناء. يجب ألا -تستخدم `Equatable` إذا كنت تريد أن تؤدي نفس الحالة المتتالية إلى تشغيل انتقالات -متعددة. +في المثال أعلاه، إذا كانت `StateA` ترث من `Equatable` فلن يحدث سوى انتقال حالة +واحد (سيُتجاهل `emit` الثاني). بشكل عام، استخدم `Equatable` عندما تريد تقليل +إعادة البناء وتحسين الأداء. ولا تستخدمه إذا كنت تحتاج أن تؤدي الحالة نفسها عند +تكرارها إلى انتقالات متعددة. -بالإضافة إلى ذلك، فإن استخدام `Equatable` يجعل اختبار الـ blocs أسهل بكثير حيث -يمكننا توقع مثيلات محددة لحالات الـ bloc بدلاً من استخدام `Matchers` أو -`Predicates`. +كما أن `Equatable` يسهّل اختبار الـ blocs، لأننا نستطيع توقّع حالات محددة +مباشرةً بدل الاعتماد على `Matchers` أو `Predicates`. -بدون `Equatable`، سيفشل الاختبار أعلاه وسيحتاج إلى إعادة كتابته على النحو -التالي: +بدون `Equatable` سيفشل الاختبار أعلاه، وسنحتاج لإعادة كتابته بهذا الشكل: ## التعامل مع الأخطاء (Handling Errors) -❔ **سؤال**: كيف يمكنني التعامل مع خطأ مع الاستمرار في عرض البيانات السابقة؟ +❔ **سؤال**: كيف أتعامل مع الخطأ مع الاستمرار في عرض البيانات السابقة؟ 💡 **إجابة**: -يعتمد هذا بشكل كبير على كيفية نمذجة حالة الـ bloc. في الحالات التي يجب فيها -الاحتفاظ بالبيانات حتى في حالة وجود خطأ، فكر في استخدام فئة حالة واحدة. +يعتمد ذلك بدرجة كبيرة على طريقة نمذجة حالة الـ bloc. إذا كنت تحتاج للاحتفاظ +بالبيانات حتى عند وقوع خطأ، فمن الأفضل استخدام فئة حالة واحدة. -سيسمح هذا للويدجتات بالوصول إلى خصائص `data` و `error` في وقت واحد، ويمكن للـ -bloc استخدام `state.copyWith` للاحتفاظ بالبيانات القديمة حتى عند حدوث خطأ. +بهذه الطريقة يمكن للـ widgets الوصول إلى `data` و `error` في الوقت نفسه، ويمكن +للـ bloc استخدام `state.copyWith` للاحتفاظ بالبيانات السابقة عند حدوث الخطأ. @@ -107,52 +104,50 @@ bloc استخدام `state.copyWith` للاحتفاظ بالبيانات الق 💡 **إجابة**: -BLoC هو نمط تصميم (design pattern) يتم تعريفه بالقواعد التالية: +الـ BLoC هو نمط تصميم يقوم على القواعد التالية: -1. مدخلات ومخرجات الـ BLoC هي مجرد تدفقات (Streams) ومصارف (Sinks). -2. يجب أن تكون التبعيات قابلة للحقن (injectable) ومستقلة عن المنصة (Platform - agnostic). -3. لا يُسمح بالتفرع الخاص بالمنصة (platform branching). -4. يمكن أن يكون التنفيذ أي شيء تريده طالما أنك تتبع القواعد المذكورة أعلاه. +1. مدخلات ومخرجات الـ BLoC هي `Streams` و`Sinks`. +2. يجب أن تكون الاعتماديات قابلة للحقن ومستقلة عن المنصة. +3. لا يُسمح بتفرعات منطقية مرتبطة بمنصة محددة. +4. تفاصيل التنفيذ مرنة ما دمت ملتزمًا بالقواعد السابقة. -إرشادات واجهة المستخدم (UI) هي: +أما إرشادات طبقة الواجهة فهي: -1. كل مكون "معقد بما فيه الكفاية" لديه BLoC مقابل. -2. يجب أن ترسل المكونات المدخلات "كما هي". -3. يجب أن تعرض المكونات المخرجات أقرب ما يمكن إلى "كما هي". -4. يجب أن يعتمد كل التفرع على مخرجات BLoC المنطقية (boolean) البسيطة. +1. كل مكوّن "معقّد بما يكفي" ينبغي أن يكون له BLoC خاص به. +2. على المكوّنات إرسال المدخلات كما هي. +3. وعلى المكوّنات عرض المخرجات بأقل قدر ممكن من التحويل. +4. وكل التفرعات المنطقية في الواجهة يجب أن تعتمد على مخارج Boolean بسيطة من الـ + BLoC. -تنفذ مكتبة Bloc نمط تصميم BLoC وتهدف إلى تجريد `RxDart` لتبسيط تجربة المطور. +مكتبة Bloc تطبق نمط BLoC، وتهدف إلى تجريد `RxDart` لتبسيط تجربة التطوير. -المبادئ الثلاثة لـ Redux هي: +أما Redux فيقوم على ثلاثة مبادئ: -1. مصدر واحد للحقيقة (Single source of truth). -2. الحالة للقراءة فقط (State is read-only). -3. يتم إجراء التغييرات باستخدام دوال نقية (pure functions). +1. مصدر واحد للحقيقة. +2. الحالة للقراءة فقط. +3. التعديلات تتم عبر دوال نقية. -تنتهك مكتبة bloc المبدأ الأول؛ ففي bloc، يتم توزيع الحالة عبر عدة blocs. علاوة -على ذلك، لا يوجد مفهوم للـ middleware في bloc، وتم تصميم bloc لجعل تغييرات -الحالة غير المتزامنة (async state changes) سهلة للغاية، مما يسمح لك بإصدار حالات -متعددة لحدث واحد. +مكتبة bloc لا تتوافق مع المبدأ الأول في Redux، لأن الحالة فيها موزعة عبر عدة +blocs. كذلك لا يوجد فيها مفهوم `middleware` بالشكل الموجود في Redux، وهي مصممة +لتسهيل تغييرات الحالة غير المتزامنة وإتاحة إصدار عدة حالات لحدث واحد. ## Bloc مقابل Provider ❔ **سؤال**: ما الفرق بين Bloc و Provider؟ -💡 **إجابة**: تم تصميم `provider` لحقن التبعية (Dependency Injection) (فهو يغلف -`InheritedWidget`). لا يزال يتعين عليك معرفة كيفية إدارة حالتك (عبر -`ChangeNotifier`، `Bloc`، `Mobx`، وما إلى ذلك...). تستخدم مكتبة Bloc حزمة -`provider` داخليًا لتسهيل توفير الـ blocs والوصول إليها في جميع أنحاء شجرة الـ -Widgets. +💡 **إجابة**: حزمة `provider` مخصّصة أساسًا لحقن الاعتماديات (وهي غلاف حول +`InheritedWidget`). ما زلت بحاجة لاختيار أسلوب إدارة الحالة بنفسك +(`ChangeNotifier` أو `Bloc` أو `Mobx` وغيرها). مكتبة Bloc تستخدم `provider` +داخليًا لتسهيل توفير الـ blocs والوصول إليها عبر شجرة الـ widgets. ## فشل BlocProvider.of() في العثور على Bloc ❔ **سؤال**: عند استخدام `BlocProvider.of(context)`، لا يمكنه العثور على الـ bloc. كيف يمكنني إصلاح ذلك؟ -💡 **إجابة**: لا يمكنك الوصول إلى bloc من نفس السياق الذي تم توفيره فيه، لذا يجب -عليك التأكد من استدعاء `BlocProvider.of()` ضمن سياق بناء فرعي (`BuildContext` -child). +💡 **إجابة**: لا يمكنك الوصول إلى bloc من نفس الـ `BuildContext` الذي قمت +بتوفيره فيه. لذلك تأكّد من استدعاء `BlocProvider.of()` من `BuildContext` تابع +(ابن). ✅ **صحيح (GOOD)** @@ -168,58 +163,56 @@ child). ❔ **سؤال**: كيف يجب أن أقوم بهيكلة مشروعي؟ -💡 **إجابة**: على الرغم من عدم وجود إجابة صحيحة/خاطئة حقًا لهذا السؤال، فإن بعض -المراجع الموصى بها هي: +💡 **إجابة**: لا توجد إجابة واحدة صحيحة أو خاطئة تمامًا لهذا السؤال، لكن هذه +مراجع مفيدة: - [I/O Photobooth](https://github.com/flutter/photobooth) - [I/O Pinball](https://github.com/flutter/pinball) - [Flutter News Toolkit](https://github.com/flutter/news_toolkit) -الشيء الأكثر أهمية هو أن يكون لديك هيكل مشروع **متسق** و **مقصود**. +الأهم أن تكون هيكلة المشروع **متسقة** و**مقصودة**. ## إضافة الأحداث داخل Bloc ❔ **سؤال**: هل من المقبول إضافة الأحداث داخل bloc؟ -💡 **إجابة**: في معظم الحالات، يجب إضافة الأحداث خارجيًا، ولكن في بعض الحالات -المختارة قد يكون من المنطقي إضافة الأحداث داخليًا. +💡 **إجابة**: في أغلب الحالات تُضاف الأحداث من خارج الـ bloc، لكن في حالات محددة +قد يكون من المنطقي إضافتها من داخله. -الحالة الأكثر شيوعًا التي تُستخدم فيها الأحداث الداخلية هي عندما يجب أن تحدث -تغييرات الحالة استجابةً للتحديثات في الوقت الفعلي من مستودع (repository). في هذه -الحالات، يكون المستودع هو المحفز لتغيير الحالة بدلاً من حدث خارجي مثل النقر على -زر. +أشيع سيناريو للأحداث الداخلية هو عندما يلزم تغيير الحالة استجابةً لتحديثات لحظية +قادمة من `repository`. هنا يكون مصدر التحفيز هو المستودع نفسه، وليس حدثًا +خارجيًا مثل ضغط زر. في المثال التالي، تعتمد حالة `MyBloc` على المستخدم الحالي الذي يتم كشفه عبر -`Stream` من `UserRepository`. يستمع `MyBloc` للتغييرات في المستخدم الحالي -ويضيف حدثًا داخليًا `_UserChanged` كلما تم إصدار مستخدم من تدفق المستخدم. +`Stream` من `UserRepository`. يستمع `MyBloc` لتغيّرات المستخدم الحالي +ويضيف حدثًا داخليًا `_UserChanged` كلما صدر مستخدم جديد من هذا الـ stream. -من خلال إضافة حدث داخلي، يمكننا أيضًا تحديد `transformer` مخصص للحدث لتحديد -كيفية معالجة أحداث `_UserChanged` المتعددة - سيتم معالجتها بالتوازي افتراضيًا. +عند إضافة حدث داخلي يمكننا أيضًا تحديد `transformer` مخصّص للتحكم في طريقة +معالجة أحداث `_UserChanged` المتعددة. افتراضيًا، تُعالج هذه الأحداث بالتوازي. -يوصى بشدة بأن تكون الأحداث الداخلية خاصة (private). هذه طريقة واضحة للإشارة إلى -أن حدثًا معينًا يُستخدم فقط داخل الـ bloc نفسه ويمنع المكونات الخارجية من معرفة -هذا الحدث. +يوصى بشدة بأن تكون الأحداث الداخلية `private`. هذا يوضح أن الحدث مخصّص للاستخدام +الداخلي فقط، ويمنع المكوّنات الخارجية من الاعتماد عليه. -يمكننا بدلاً من ذلك تعريف حدث خارجي `Started` واستخدام واجهة برمجة التطبيقات -`emit.forEach` للتعامل مع التفاعل مع تحديثات المستخدم في الوقت الفعلي: +يمكن أيضًا تعريف حدث خارجي `Started` واستخدام `emit.forEach` للتعامل مع تحديثات +المستخدم اللحظية: -فوائد هذا النهج هي: +مزايا هذا النهج: -- لا نحتاج إلى حدث داخلي `_UserChanged`. -- لا نحتاج إلى إدارة `StreamSubscription` يدويًا. -- لدينا سيطرة كاملة على متى يشترك الـ bloc في تدفق تحديثات المستخدم. +- لا حاجة لحدث داخلي `_UserChanged`. +- لا حاجة لإدارة `StreamSubscription` يدويًا. +- تحكم كامل في توقيت اشتراك الـ bloc في stream تحديثات المستخدم. -عيوب هذا النهج هي: +عيوب هذا النهج: -- لا يمكننا بسهولة `pause` أو `resume` الاشتراك. -- نحتاج إلى كشف حدث عام `Started` يجب إضافته خارجيًا. -- لا يمكننا استخدام `transformer` مخصص لضبط كيفية تفاعلنا مع تحديثات المستخدم. +- لا يمكن إيقاف (`pause`) الاشتراك أو استئنافه (`resume`) بسهولة. +- نحتاج إلى كشف حدث عام `Started` ليُضاف من الخارج. +- لا يمكن استخدام `transformer` مخصص لضبط طريقة التعامل مع تحديثات المستخدم. ## كشف الدوال العامة (Exposing Public Methods) @@ -228,9 +221,8 @@ cubit الخاصة بي؟ 💡 **إجابة** -عند إنشاء cubit، يوصى بكشف الدوال العامة فقط لغرض تشغيل تغييرات الحالة. ونتيجة -لذلك، يجب أن تعيد جميع الدوال العامة على مثيل cubit بشكل عام `void` أو -`Future`. +عند إنشاء cubit، يُنصح بأن تكون الدوال العامة مخصّصة فقط لتحفيز تغيّر الحالة. +لذلك، من الأفضل غالبًا أن تعيد هذه الدوال `void` أو `Future`. -عند إنشاء bloc، يوصى بتجنب كشف أي دوال عامة مخصصة وبدلاً من ذلك إخطار الـ bloc -بالأحداث عن طريق استدعاء `add`. +وعند إنشاء bloc، يُنصح بتجنب كشف دوال عامة مخصّصة، والاكتفاء بإبلاغه بالأحداث +عبر استدعاء `add`. diff --git a/docs/src/content/docs/ar/flutter-bloc-concepts.mdx b/docs/src/content/docs/ar/flutter-bloc-concepts.mdx index daa05925083..2e9f8728e21 100644 --- a/docs/src/content/docs/ar/flutter-bloc-concepts.mdx +++ b/docs/src/content/docs/ar/flutter-bloc-concepts.mdx @@ -55,41 +55,40 @@ import WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSn **BlocBuilder** هو ويدجت (Widget) في Flutter يتطلب `Bloc` ودالة `builder`. يتعامل `BlocBuilder` مع بناء الـ Widget استجابةً للحالات الجديدة (new states). -يشبه `BlocBuilder` إلى حد كبير `StreamBuilder` ولكنه يمتلك واجهة برمجية (API) -أبسط لتقليل كمية الكود المتكرر (boilerplate code) المطلوب. من المحتمل أن يتم -استدعاء دالة `builder` عدة مرات، ويجب أن تكون +يشبه `BlocBuilder` إلى حد كبير `StreamBuilder` لكنه يوفر واجهة (API) أبسط لتقليل +كمية الكود المتكرر (boilerplate) المطلوبة. قد يتم استدعاء دالة `builder` مرات +عديدة، لذلك يُفضل أن تكون [دالة نقية (pure function)](https://en.wikipedia.org/wiki/Pure_function) تعيد ويدجت استجابةً للحالة. -راجع `BlocListener` إذا كنت ترغب في "القيام" بأي شيء استجابةً لتغيرات الحالة مثل -التنقل (navigation)، أو إظهار مربع حوار (dialog)، وما إلى ذلك... +راجع `BlocListener` إذا كنت تريد "تنفيذ" شيء استجابةً لتغيرات الحالة مثل التنقل +(navigation) أو إظهار مربع حوار (dialog) وغيرها. إذا تم حذف معامل `bloc`، فسيقوم `BlocBuilder` تلقائيًا بإجراء بحث (lookup) باستخدام `BlocProvider` و `BuildContext` الحالي. -قم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc سيكون نطاقه (scoped) مقتصراً +قم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc سيكون نطاقه (scoped) مقتصرًا على ويدجت واحد ولا يمكن الوصول إليه عبر `BlocProvider` أبوي و `BuildContext` الحالي. -للحصول على تحكم دقيق في وقت استدعاء دالة `builder`، يمكن توفير دالة اختيارية -تسمى `buildWhen`. تأخذ `buildWhen` حالة الـ bloc السابقة وحالة الـ bloc الحالية -وتعيد قيمة منطقية (boolean). إذا أعادت `buildWhen` القيمة `true`، فسيتم استدعاء -`builder` مع `state` وسيتم إعادة بناء الـ Widget. إذا أعادت `buildWhen` القيمة -`false`، فلن يتم استدعاء `builder` مع `state` ولن تحدث إعادة بناء. +لتحكم أدق في وقت استدعاء دالة `builder`، يمكن تمرير دالة اختيارية `buildWhen`. +تأخذ `buildWhen` حالة الـ bloc السابقة والحالة الحالية وتعيد قيمة منطقية +(boolean). إذا أعادت `true` فسيتم استدعاء `builder` وإعادة بناء الـ Widget، وإذا +أعادت `false` فلن يتم استدعاء `builder` ولن تحدث إعادة بناء. ### BlocSelector -**BlocSelector** هو ويدجت في Flutter يشبه `BlocBuilder` ولكنه يسمح للمطورين -بتصفية التحديثات عن طريق اختيار قيمة جديدة بناءً على حالة الـ bloc الحالية. يتم -منع عمليات البناء غير الضرورية إذا لم تتغير القيمة المختارة. يجب أن تكون القيمة -المختارة غير قابلة للتغيير (immutable) لكي يتمكن `BlocSelector` من تحديد ما إذا -كان يجب استدعاء `builder` مرة أخرى بدقة. +**BlocSelector** هو ويدجت في Flutter يشبه `BlocBuilder` لكنه يسمح بتصفية +التحديثات عبر اختيار قيمة جديدة بناءً على حالة الـ bloc الحالية. يتم منع عمليات +البناء غير الضرورية إذا لم تتغير القيمة المختارة. يجب أن تكون القيمة المختارة +غير قابلة للتغيير (immutable) حتى يتمكن `BlocSelector` من تحديد ما إذا كان يجب +استدعاء `builder` مجددًا بدقة. إذا تم حذف معامل `bloc`، فسيقوم `BlocSelector` تلقائيًا بإجراء بحث باستخدام `BlocProvider` و `BuildContext` الحالي. @@ -99,19 +98,18 @@ import WeatherPageSnippet from '~/components/concepts/flutter-bloc/WeatherPageSn ### BlocProvider **BlocProvider** هو ويدجت في Flutter يوفر bloc لأبنائه عبر -`BlocProvider.of(context)`. يتم استخدامه كويدجت لحقن التبعية (Dependency +`BlocProvider.of(context)`. يُستخدم كويدجت لحقن التبعية (Dependency Injection - DI) بحيث يمكن توفير مثيل واحد من الـ bloc لعدة ويدجتات داخل شجرة فرعية (subtree). -في معظم الحالات، يجب استخدام `BlocProvider` لإنشاء blocs جديدة ستكون متاحة لبقية -الشجرة الفرعية. في هذه الحالة، بما أن `BlocProvider` هو المسؤول عن إنشاء الـ -bloc، فإنه سيتولى تلقائيًا إغلاق الـ bloc. +في أغلب الحالات، يجب استخدام `BlocProvider` لإنشاء blocs جديدة ستكون متاحة لبقية +الشجرة الفرعية. وبما أن `BlocProvider` هو المسؤول عن الإنشاء، فسيتولى تلقائيًا +إغلاق الـ bloc. -بشكل افتراضي، سيقوم `BlocProvider` بإنشاء الـ bloc بشكل كسول (lazily)، مما يعني -أن `create` سيتم تنفيذه عند البحث عن الـ bloc عبر -`BlocProvider.of(context)`. +بشكل افتراضي، ينشئ `BlocProvider` الـ bloc بشكل كسول (lazily)، أي أن `create` +سيتم تنفيذه عند البحث عن الـ bloc عبر `BlocProvider.of(context)`. لتجاوز هذا السلوك وإجبار `create` على العمل فورًا، يمكن تعيين `lazy` إلى `false`. @@ -119,21 +117,20 @@ bloc، فإنه سيتولى تلقائيًا إغلاق الـ bloc. في بعض الحالات، يمكن استخدام `BlocProvider` لتوفير bloc موجود بالفعل لجزء جديد -من شجرة الـ Widgets. سيتم استخدام هذا بشكل شائع عندما يحتاج bloc موجود إلى أن -يكون متاحًا لمسار (route) جديد. في هذه الحالة، لن يقوم `BlocProvider` بإغلاق الـ -bloc تلقائيًا لأنه لم يقم بإنشائه. +من شجرة الـ Widgets (مثلاً عند فتح Route جديد). في هذه الحالة، لن يقوم +`BlocProvider` بإغلاق الـ bloc تلقائيًا لأنه لم يقم بإنشائه. -بعد ذلك، من أي من `ChildA` أو `ScreenA`، يمكننا استرداد `BlocA` باستخدام: +بعد ذلك، من أي من `ChildA` أو `ScreenA` يمكننا استرداد `BlocA` باستخدام: ### MultiBlocProvider -**MultiBlocProvider** هو ويدجت في Flutter يدمج عدة ويدجتات `BlocProvider` في -ويدجت واحد. يحسن `MultiBlocProvider` من قابلية القراءة ويزيل الحاجة إلى تداخل -(nesting) عدة `BlocProviders`. باستخدام `MultiBlocProvider` يمكننا الانتقال من: +**MultiBlocProvider** هو ويدجت في Flutter يدمج عدة `BlocProvider` في ويدجت واحد. +يحسن قابلية القراءة ويزيل الحاجة إلى تداخل (nesting) عدة `BlocProvider`. +باستخدام `MultiBlocProvider` يمكننا الانتقال من: @@ -151,36 +148,32 @@ bloc تلقائيًا لأنه لم يقم بإنشائه. ### BlocListener **BlocListener** هو ويدجت في Flutter يأخذ `BlocWidgetListener` و `Bloc` اختياري، -ويستدعي `listener` استجابةً لتغيرات الحالة في الـ bloc. يجب استخدامه للوظائف -التي تحتاج إلى الحدوث مرة واحدة لكل تغيير في الحالة، مثل التنقل، إظهار -`SnackBar`، إظهار `Dialog`، وما إلى ذلك... +ويستدعي `listener` استجابةً لتغيرات الحالة في الـ bloc. يُستخدم للوظائف التي يجب +أن تحدث مرة واحدة لكل تغيير حالة مثل التنقل، إظهار `SnackBar` أو `Dialog`. يتم استدعاء `listener` مرة واحدة فقط لكل تغيير في الحالة (**لا** يشمل الحالة -الأولية) على عكس `builder` في `BlocBuilder`، وهي دالة `void`. +الأولية) على عكس `builder` في `BlocBuilder`، وهو دالة `void`. إذا تم حذف معامل `bloc`، فسيقوم `BlocListener` تلقائيًا بإجراء بحث باستخدام `BlocProvider` و `BuildContext` الحالي. -قم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc لا يمكن الوصول إليه بطريقة -أخرى عبر `BlocProvider` و `BuildContext` الحالي. +قم بتحديد الـ bloc فقط إذا كنت ترغب في توفير bloc لا يمكن الوصول إليه عبر +`BlocProvider` و `BuildContext` الحالي. -للحصول على تحكم دقيق في وقت استدعاء دالة `listener`، يمكن توفير دالة اختيارية -تسمى `listenWhen`. تأخذ `listenWhen` حالة الـ bloc السابقة وحالة الـ bloc -الحالية وتعيد قيمة منطقية. إذا أعادت `listenWhen` القيمة `true`، فسيتم استدعاء -`listener` مع `state`. إذا أعادت `listenWhen` القيمة `false`، فلن يتم استدعاء -`listener` مع `state`. +لتحكم أدق في وقت استدعاء `listener`، يمكن تمرير `listenWhen`. تأخذ `listenWhen` +حالة الـ bloc السابقة والحالة الحالية وتعيد قيمة منطقية. إذا أعادت `true` فسيتم +استدعاء `listener`، وإذا أعادت `false` فلن يتم استدعاؤه. ### MultiBlocListener -**MultiBlocListener** هو ويدجت في Flutter يدمج عدة ويدجتات `BlocListener` في -ويدجت واحد. يحسن `MultiBlocListener` من قابلية القراءة ويزيل الحاجة إلى تداخل -عدة `BlocListeners`. باستخدام `MultiBlocListener` يمكننا الانتقال من: +**MultiBlocListener** يدمج عدة `BlocListener` في ويدجت واحد لتحسين قابلية +القراءة وتجنب التداخل. باستخدامه يمكننا الانتقال من: @@ -197,51 +190,45 @@ bloc تلقائيًا لأنه لم يقم بإنشائه. ### BlocConsumer -**BlocConsumer** يكشف عن `builder` و `listener` من أجل التفاعل مع الحالات -الجديدة. يشبه `BlocConsumer` تداخل `BlocListener` و `BlocBuilder` ولكنه يقلل من -كمية الكود المتكرر المطلوب. يجب استخدام `BlocConsumer` فقط عندما يكون من الضروري -إعادة بناء واجهة المستخدم (UI) وتنفيذ تفاعلات أخرى لتغيرات الحالة في الـ bloc. -يأخذ `BlocConsumer` دالة `BlocWidgetBuilder` و `BlocWidgetListener` مطلوبتين، و -`bloc` و `BlocBuilderCondition` و `BlocListenerCondition` اختيارية. +**BlocConsumer** يجمع بين `builder` و `listener` للتفاعل مع الحالات الجديدة. +يشبه استخدام `BlocListener` و `BlocBuilder` معًا لكنه يقلل من الكود المتكرر. +يُستخدم فقط عندما تحتاج إلى إعادة بناء UI وتنفيذ تفاعل آخر مع تغيرات الحالة في +نفس الوقت. إذا تم حذف معامل `bloc`، فسيقوم `BlocConsumer` تلقائيًا بإجراء بحث باستخدام `BlocProvider` و `BuildContext` الحالي. -يمكن تطبيق `listenWhen` و `buildWhen` اختياريًا للتحكم بشكل أدق في وقت استدعاء -`listener` و `builder`. سيتم استدعاء `listenWhen` و `buildWhen` عند كل تغيير في -`state` الخاص بالـ `bloc`. يأخذ كل منهما `state` السابق و `state` الحالي ويجب أن -يعيد قيمة `bool` تحدد ما إذا كان سيتم استدعاء دالة `builder` و/أو `listener`. -سيتم تهيئة `state` السابق إلى `state` الخاص بالـ `bloc` عند تهيئة -`BlocConsumer`. يعتبر `listenWhen` و `buildWhen` اختياريين، وإذا لم يتم -تطبيقهما، فسيتم تعيينهما افتراضيًا على `true`. +يمكن تمرير `listenWhen` و `buildWhen` اختياريًا للتحكم بشكل أدق في وقت استدعاء +`listener` و `builder`. سيتم استدعاؤهما عند كل تغيير في `state` الخاص بالـ bloc. +كل منهما يأخذ الحالة السابقة والحالية ويعيد `bool`. إذا لم يتم توفيرهما فالقيمة +الافتراضية هي `true`. ### RepositoryProvider **RepositoryProvider** هو ويدجت في Flutter يوفر مستودعًا (repository) لأبنائه -عبر `RepositoryProvider.of(context)`. يتم استخدامه كويدجت لحقن التبعية (DI) -بحيث يمكن توفير مثيل واحد من المستودع لعدة ويدجتات داخل شجرة فرعية. يجب استخدام -`BlocProvider` لتوفير الـ blocs بينما يجب استخدام `RepositoryProvider` فقط -للمستودعات. +عبر `RepositoryProvider.of(context)`. يُستخدم لحقن التبعية (DI) لتوفير مثيل +واحد من المستودع لعدة ويدجتات داخل شجرة فرعية. يُستخدم `BlocProvider` للـ blocs +بينما يُستخدم `RepositoryProvider` للمستودعات فقط. +ثم من `ChildA` يمكننا استرداد مثيل `Repository` باستخدام: + -يمكن للمستودعات التي تدير موارد يجب التخلص منها (dispose) أن تفعل ذلك عبر دالة -الاستدعاء العكسي (callback) المسماة `dispose`: +يمكن للمستودعات التي تدير موارد يجب التخلص منها (dispose) القيام بذلك عبر دالة +`dispose`: ### MultiRepositoryProvider -**MultiRepositoryProvider** هو ويدجت في Flutter يدمج عدة ويدجتات -`RepositoryProvider` في ويدجت واحد. يحسن `MultiRepositoryProvider` من قابلية -القراءة ويزيل الحاجة إلى تداخل عدة `RepositoryProvider`. باستخدام -`MultiRepositoryProvider` يمكننا الانتقال من: +**MultiRepositoryProvider** يدمج عدة `RepositoryProvider` في ويدجت واحد لتحسين +قابلية القراءة وتجنب التداخل. باستخدامه يمكننا الانتقال من: @@ -270,7 +257,7 @@ bloc تلقائيًا لأنه لم يقم بإنشائه. في هذه المرحلة، نجحنا في فصل طبقة العرض (presentational layer) عن طبقة منطق الأعمال (business logic layer). لاحظ أن ويدجت `CounterPage` لا يعرف شيئًا عما يحدث عندما ينقر المستخدم على الأزرار. يخبر الـ Widget ببساطة `CounterBloc` بأن -المستخدم قد ضغط على زر الزيادة أو النقصان. +المستخدم ضغط زر الزيادة أو النقصان. ## استخدام RepositoryProvider @@ -279,29 +266,29 @@ bloc تلقائيًا لأنه لم يقم بإنشائه. -في ملف `main.dart`، نستدعي `runApp` مع ويدجت `WeatherApp` الخاص بنا. +في ملف `main.dart` نستدعي `runApp` مع ويدجت `WeatherApp`. -سنقوم بحقن مثيل `WeatherRepository` الخاص بنا في شجرة الـ Widgets عبر +سنقوم بحقن مثيل `WeatherRepository` في شجرة الـ Widgets عبر `RepositoryProvider`. -عند إنشاء مثيل لـ bloc، يمكننا الوصول إلى مثيل المستودع عبر `context.read` وحقن -المستودع في الـ bloc عبر المُنشئ (constructor). +عند إنشاء bloc، يمكننا الوصول إلى مثيل المستودع عبر `context.read` وحقن المستودع +في الـ bloc عبر المُنشئ (constructor). :::tip إذا كان لديك أكثر من مستودع واحد، يمكنك استخدام `MultiRepositoryProvider` لتوفير -مثيلات مستودعات متعددة للشجرة الفرعية. +عدة مستودعات للشجرة الفرعية. ::: :::note -استخدم دالة الاستدعاء العكسي `dispose` للتعامل مع تحرير أي موارد عند إزالة -`RepositoryProvider` من شجرة الـ Widgets (unmounted). +استخدم `dispose` للتعامل مع تحرير الموارد عند إزالة `RepositoryProvider` من شجرة +الـ Widgets. ::: @@ -312,42 +299,38 @@ bloc تلقائيًا لأنه لم يقم بإنشائه. [دوال الامتداد](https://dart.dev/guides/language/extension-methods)، التي تم تقديمها في Dart 2.7، هي طريقة لإضافة وظائف إلى المكتبات الموجودة. في هذا القسم، -سنلقي نظرة على دوال الامتداد المضمنة في `package:flutter_bloc` وكيف يمكن -استخدامها. +سنلقي نظرة على دوال الامتداد في `package:flutter_bloc` وكيف يمكن استخدامها. -تعتمد `flutter_bloc` على حزمة -[package:provider](https://pub.dev/packages/provider) التي تبسط استخدام +تعتمد `flutter_bloc` على [package:provider](https://pub.dev/packages/provider) +والتي تبسط استخدام [`InheritedWidget`](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html). -داخليًا، تستخدم `package:flutter_bloc` حزمة `package:provider` لتطبيق ويدجتات: -`BlocProvider`، `MultiBlocProvider`، `RepositoryProvider`، و -`MultiRepositoryProvider`. تصدر `package:flutter_bloc` امتدادات `ReadContext` و -`WatchContext` و `SelectContext` من حزمة `package:provider`. +داخليًا، تستخدم `package:flutter_bloc` حزمة `provider` لتنفيذ: `BlocProvider` و +`MultiBlocProvider` و `RepositoryProvider` و `MultiRepositoryProvider`. كما تصدر +امتدادات `ReadContext` و `WatchContext` و `SelectContext` من `provider`. :::note -تعرف على المزيد حول حزمة -[`package:provider`](https://pub.dev/packages/provider). +تعرف على المزيد حول [`package:provider`](https://pub.dev/packages/provider). ::: ### context.read -تقوم `context.read()` بالبحث عن أقرب مثيل سلف (ancestor instance) من النوع -`T` وهي مكافئة وظيفيًا لـ `BlocProvider.of(context)`. تُستخدم `context.read` -بشكل شائع لاسترداد مثيل bloc من أجل إضافة حدث (event) داخل دوال الاستدعاء العكسي -`onPressed`. +تقوم `context.read()` بالبحث عن أقرب مثيل سلف من النوع `T` وهي مكافئة وظيفيًا +لـ `BlocProvider.of(context)`. تُستخدم غالبًا لاسترداد مثيل bloc لإضافة حدث +داخل `onPressed`. :::note -`context.read()` لا تستمع إلى `T` - إذا تغير الكائن (Object) المقدم من النوع -`T`، فلن تؤدي `context.read` إلى إعادة بناء الـ Widget. +`context.read()` لا تستمع إلى `T` - إذا تغير الكائن المقدم من النوع `T` فلن +تؤدي `context.read` إلى إعادة بناء الـ Widget. ::: #### الاستخدام -✅ **افعل** استخدام `context.read` لإضافة الأحداث في دوال الاستدعاء العكسي. +✅ **افعل** استخدام `context.read` لإضافة الأحداث في callbacks. ```dart onPressed() { @@ -355,7 +338,7 @@ onPressed() { } ``` -❌ **تجنب** استخدام `context.read` لاسترداد الحالة داخل دالة `build`. +❌ **تجنب** استخدام `context.read` لاسترداد الحالة داخل `build`. ```dart @override @@ -370,31 +353,30 @@ Widget build(BuildContext context) { :::caution -استخدم `BlocBuilder` أو `context.watch` بدلاً من ذلك من أجل إعادة البناء -استجابةً لتغيرات الحالة. +استخدم `BlocBuilder` أو `context.watch` بدلًا من ذلك لإعادة البناء عند تغير +الحالة. ::: ### context.watch -مثل `context.read()`، توفر `context.watch()` أقرب مثيل سلف من النوع `T`، -ولكنها تستمع أيضًا إلى التغييرات على المثيل. وهي مكافئة وظيفيًا لـ +مثل `context.read()`، توفر `context.watch()` أقرب مثيل سلف من النوع `T` +لكنها تستمع أيضًا للتغييرات على المثيل. وهي مكافئة وظيفيًا لـ `BlocProvider.of(context, listen: true)`. -إذا تغير الكائن المقدم من النوع `T`، فستؤدي `context.watch` إلى إعادة بناء +إذا تغير الكائن المقدم من النوع `T` فستؤدي `context.watch` إلى إعادة بناء (rebuild). :::caution -يمكن الوصول إلى `context.watch` فقط ضمن دالة `build` الخاصة بـ `StatelessWidget` -أو فئة `State`. +`context.watch` متاحة فقط داخل `build` في `StatelessWidget` أو `State`. ::: #### الاستخدام -✅ **افعل** استخدام `BlocBuilder` بدلاً من `context.watch` لتحديد نطاق عمليات -إعادة البناء بشكل صريح. +✅ **افعل** استخدام `BlocBuilder` بدلًا من `context.watch` لتحديد نطاق إعادة +البناء بشكل صريح. ```dart Widget build(BuildContext context) { @@ -411,7 +393,7 @@ Widget build(BuildContext context) { } ``` -بدلاً من ذلك، استخدم `Builder` لتحديد نطاق عمليات إعادة البناء. +بدلًا من ذلك، استخدم `Builder` لتحديد نطاق إعادة البناء. ```dart @override @@ -430,7 +412,7 @@ Widget build(BuildContext context) { } ``` -✅ **افعل** استخدام `Builder` و `context.watch` كـ `MultiBlocBuilder`. +✅ **افعل** استخدام `Builder` و `context.watch` كبديل لـ `MultiBlocBuilder`. ```dart Builder( @@ -444,8 +426,7 @@ Builder( ); ``` -❌ **تجنب** استخدام `context.watch` عندما لا يعتمد الـ Widget الأب (parent -widget) في دالة `build` على الحالة. +❌ **تجنب** استخدام `context.watch` عندما لا يعتمد الـ Widget الأب على الحالة. ```dart @override @@ -463,17 +444,16 @@ Widget build(BuildContext context) { :::caution -سيؤدي استخدام `context.watch` في جذر دالة `build` إلى إعادة بناء الـ Widget -بالكامل عندما تتغير حالة الـ bloc. +سيؤدي استخدام `context.watch` في جذر `build` إلى إعادة بناء الـ Widget بالكامل +عند تغير حالة الـ bloc. ::: ### context.select -تمامًا مثل `context.watch()`، توفر -`context.select(R function(T value))` أقرب مثيل سلف من النوع `T` وتستمع -إلى التغييرات على `T`. على عكس `context.watch`، تسمح لك `context.select` -بالاستماع إلى التغييرات في جزء أصغر من الحالة. +تمامًا مثل `context.watch()`، توفر `context.select(...)` أقرب مثيل سلف +من النوع `T` وتستمع إلى التغييرات عليه، لكنها تسمح بالاستماع إلى جزء أصغر من +الحالة. ```dart Widget build(BuildContext context) { @@ -482,13 +462,12 @@ Widget build(BuildContext context) { } ``` -ما ورد أعلاه سيؤدي فقط إلى إعادة بناء الـ Widget عندما تتغير خاصية `name` الخاصة -بحالة `ProfileBloc`. +سيُعاد بناء الـ Widget فقط عند تغير خاصية `name` في حالة `ProfileBloc`. #### الاستخدام -✅ **افعل** استخدام `BlocSelector` بدلاً من `context.select` لتحديد نطاق عمليات -إعادة البناء بشكل صريح. +✅ **افعل** استخدام `BlocSelector` بدلًا من `context.select` لتحديد نطاق إعادة +البناء بشكل صريح. ```dart Widget build(BuildContext context) { @@ -506,7 +485,7 @@ Widget build(BuildContext context) { } ``` -بدلاً من ذلك، استخدم `Builder` لتحديد نطاق عمليات إعادة البناء. +بدلًا من ذلك، استخدم `Builder` لتحديد نطاق إعادة البناء. ```dart @override @@ -525,8 +504,7 @@ Widget build(BuildContext context) { } ``` -❌ **تجنب** استخدام `context.select` عندما لا يعتمد الـ Widget الأب في دالة -`build` على الحالة. +❌ **تجنب** استخدام `context.select` عندما لا يعتمد الـ Widget الأب على الحالة. ```dart @override @@ -544,7 +522,7 @@ Widget build(BuildContext context) { :::caution -سيؤدي استخدام `context.select` في جذر دالة `build` إلى إعادة بناء الـ Widget -بالكامل عندما يتغير التحديد (selection). +سيؤدي استخدام `context.select` في جذر `build` إلى إعادة بناء الـ Widget بالكامل +عند تغير الاختيار (selection). ::: diff --git a/docs/src/content/docs/ar/getting-started.mdx b/docs/src/content/docs/ar/getting-started.mdx index ae2c5748fa3..3c180d505e7 100644 --- a/docs/src/content/docs/ar/getting-started.mdx +++ b/docs/src/content/docs/ar/getting-started.mdx @@ -1,41 +1,41 @@ --- title: دليل البدء -description: كل ما تحتاجه لبدء الانشاء مع بلوك +description: كل ما تحتاجه لبدء البناء باستخدام Bloc. --- import InstallationTabs from '~/components/getting-started/InstallationTabs.astro'; import ImportTabs from '~/components/getting-started/ImportTabs.astro'; -## الباكيتات +## الحزم -بيئة بلوك تتكون من عدة باكيتات مدرجة أدناه +يتكوّن نظام Bloc من عدة حزم مدرجة أدناه: -| الباكيتة | الوصف | الرابط | +| الحزمة | الوصف | الرابط | | ------------------------------------------------------------------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc) | مكونات انجيولار | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc) | -| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc) | واجهات برمجة تطبيقات دارت الأساسية | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc) | -| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | محولات الحدث | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) | -| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint) | Custom Linter | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint) | -| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test) | واجهات برمجة تطبيقات للاختبارات | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test) | -| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools) | Command-line Tools | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools) | -| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc) | مكونات فلاتار | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc) | -| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc) | دعم للتخزين المؤقت/الاستمرارية | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc) | -| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc) | دعم للتراجع/الإعادة | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc) | - -## التحميل +| [angular_bloc](https://github.com/felangel/bloc/tree/master/packages/angular_bloc) | مكونات AngularDart | [![pub package](https://img.shields.io/pub/v/angular_bloc.svg)](https://pub.dev/packages/angular_bloc) | +| [bloc](https://github.com/felangel/bloc/tree/master/packages/bloc) | واجهات برمجة تطبيقات Dart الأساسية | [![pub package](https://img.shields.io/pub/v/bloc.svg)](https://pub.dev/packages/bloc) | +| [bloc_concurrency](https://github.com/felangel/bloc/tree/master/packages/bloc_concurrency) | محولات الأحداث | [![pub package](https://img.shields.io/pub/v/bloc_concurrency.svg)](https://pub.dev/packages/bloc_concurrency) | +| [bloc_lint](https://github.com/felangel/bloc/tree/master/packages/bloc_lint) | مدقق مخصص | [![pub package](https://img.shields.io/pub/v/bloc_lint.svg)](https://pub.dev/packages/bloc_lint) | +| [bloc_test](https://github.com/felangel/bloc/tree/master/packages/bloc_test) | واجهات برمجة تطبيقات للاختبار | [![pub package](https://img.shields.io/pub/v/bloc_test.svg)](https://pub.dev/packages/bloc_test) | +| [bloc_tools](https://github.com/felangel/bloc/tree/master/packages/bloc_tools) | أدوات سطر الأوامر | [![pub package](https://img.shields.io/pub/v/bloc_tools.svg)](https://pub.dev/packages/bloc_tools) | +| [flutter_bloc](https://github.com/felangel/bloc/tree/master/packages/flutter_bloc) | عناصر واجهة Flutter | [![pub package](https://img.shields.io/pub/v/flutter_bloc.svg)](https://pub.dev/packages/flutter_bloc) | +| [hydrated_bloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc) | دعم التخزين المؤقت/الاستمرارية | [![pub package](https://img.shields.io/pub/v/hydrated_bloc.svg)](https://pub.dev/packages/hydrated_bloc) | +| [replay_bloc](https://github.com/felangel/bloc/tree/master/packages/replay_bloc) | دعم التراجع/الإعادة | [![pub package](https://img.shields.io/pub/v/replay_bloc.svg)](https://pub.dev/packages/replay_bloc) | + +## التثبيت :::note -من أجل أن تستطيع استخدام بلوك، يجب أن تكون لديك -[عدة تطوير برامج دارت](https://dart.dev/get-dart) محملة على جهازك +من أجل البدء باستخدام bloc، يجب أن يكون +[Dart SDK مُثبت](https://dart.dev/get-dart) على جهازك. ::: -## الاستورادات +## الاستيراد -الآن بعد أن قمنا بتحميل بلوك بنجاح، يمكننا إنشاء `main.dart` الخاص بنا واستيراد -باكيتة `bloc` الخاصة بنا +الآن بعد أن قمنا بتثبيت bloc بنجاح، يمكننا إنشاء ملف `main.dart` واستيراد حزمة +`bloc` المناسبة. diff --git a/docs/src/content/docs/ar/index.mdx b/docs/src/content/docs/ar/index.mdx index 38b5bf4b293..062af06c729 100644 --- a/docs/src/content/docs/ar/index.mdx +++ b/docs/src/content/docs/ar/index.mdx @@ -1,18 +1,18 @@ --- template: splash -title: Bloc State Management Library +title: مكتبة Bloc لإدارة الحالة description: - Official documentation for the bloc state management library. Support for - Dart, Flutter, and AngularDart. Includes examples and tutorials. + التوثيق الرسمي لمكتبة Bloc لإدارة الحالة. تدعم Dart وFlutter وAngularDart. + يتضمن أمثلة ودروسًا تعليمية. banner: content: | - ✨ قم بزيارة - متجر بلوك ✨ + ✨ تفضل بزيارة + متجر Bloc ✨ editUrl: false lastUpdated: false hero: title: Bloc v9.2.0 - tagline: مكتبة لإدارة الحال في dart ممكن التوقع لنتيجتها + tagline: مكتبة لإدارة الحالة في Dart تعتمد على قابلية التنبؤ. image: alt: Bloc logo file: ~/assets/bloc.svg @@ -21,7 +21,7 @@ hero: link: /ar/getting-started/ variant: primary icon: rocket - - text: عاين على جيت هب + - text: عرض على GitHub link: https://github.com/felangel/bloc icon: github variant: secondary @@ -42,45 +42,45 @@ import Discord from '~/components/landing/Discord.astro'; ```sh - # أضف بلوك إلى مشروعك + # .إلى مشروعك bloc أضف dart pub add bloc ``` -يحتوي [دليل البدء](/ar/getting-started) لدينا، على إرشادات خطوية حول كيفية بدء -استخدام بلوك في بضع دقائق قليلة +يوفر [دليل البدء](/ar/getting-started) إرشادات خطوة بخطوة حول كيفية بدء استخدام +Bloc خلال بضع دقائق فقط. - أكمل [البرامج التعليمية الرسمية](/ar/tutorials/flutter-counter) لتتعلم أفضل - الممارسات وإنشاء مجموعة متنوعة من التطبيقات المختلفة التي تدعمها بلوك + أكمل [الدروس الرسمية](/ar/tutorials/flutter-counter) لتتعلم أفضل الممارسات + وبناء مجموعة متنوعة من التطبيقات المعتمدة على Bloc. - - استكشف [نماذج تطبيقات](https://github.com/felangel/bloc/tree/master/examples) - عالية الجودة، تم اختبارها بالكامل مثل العداد، والمؤقت، والقائمة اللانهائية، - والطقس، والمهام، والمزيد + + استكشف [أمثلة تطبيقات عالية الجودة ومختبرة + بالكامل](https://github.com/felangel/bloc/tree/master/examples) مثل العداد، + والمؤقت، والقائمة اللانهائية، والطقس، والمهام، والمزيد. - + - - [لماذا بلوك؟](/ar/why-bloc) + - [لماذا Bloc؟](/ar/why-bloc) - [المفاهيم الأساسية](/ar/bloc-concepts) - - [الطبقات](/ar/architecture) + - [البنية المعمارية](/ar/architecture) - [الاختبارات](/ar/testing) - [اتفاقيات التسمية](/ar/naming-conventions) - - [FAQs](/ar/faqs) + - [الأسئلة الشائعة](/ar/faqs) - - [الدمج مع ڤي اس كود](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) - - [الدمج مع انتيللي چيه](https://plugins.jetbrains.com/plugin/12129-bloc) - - [Neovim Integration](https://github.com/wa11breaker/flutter-bloc.nvim) - - [الدمج مع ميسون سي إل آي](https://github.com/felangel/bloc/blob/master/bricks/README.md) + - [التكامل مع VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) + - [التكامل مع IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) + - [التكامل مع Neovim](https://github.com/wa11breaker/flutter-bloc.nvim) + - [التكامل مع Mason CLI](https://github.com/felangel/bloc/blob/master/bricks/README.md) - [قوالب مخصصة](https://brickhub.dev/search?q=bloc) - - [ادوات المطورين](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md) + - [أدوات المطورين](https://github.com/felangel/bloc/blob/master/packages/bloc_tools/README.md) - + diff --git a/docs/src/content/docs/ar/lint-rules/avoid_build_context_extensions.mdx b/docs/src/content/docs/ar/lint-rules/avoid_build_context_extensions.mdx index c7589ed670b..f07b416c749 100644 --- a/docs/src/content/docs/ar/lint-rules/avoid_build_context_extensions.mdx +++ b/docs/src/content/docs/ar/lint-rules/avoid_build_context_extensions.mdx @@ -1,6 +1,6 @@ --- title: تجنب امتدادات BuildContext (Avoid BuildContext Extensions) -description: قاعدة avoid_build_context_extensions البرمجية. +description: قاعدة `avoid_build_context_extensions`. --- import { Badge } from '@astrojs/starlight/components'; @@ -13,21 +13,20 @@ import GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/ -تجنب استخدام امتدادات (`BuildContext` extensions) للوصول إلى نُسخ (`instances`) -من `Bloc` أو `Cubit`. +تجنب استخدام امتدادات `BuildContext` للوصول إلى instances من `Bloc` أو `Cubit`. :::note -تم تقديم قاعدة التحليل البرمجي (lint rule) هذه في الإصدار `0.3.0` من حزمة +تم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.0` من [`package:bloc_lint`](https://pub.dev/packages/bloc_lint). ::: -## الأساس المنطقي (Rationale) +## المبرر (Rationale) -من أجل الاتساق ولغرض أن تكون أكثر وضوحًا وصراحةً، يُفضل استخدام الطرق الأساسية -مباشرةً بدلاً من امتدادات `BuildContext`. هذا مفيد أيضًا للاختبار لأنه **لا -يمكن** محاكاة (`mock`) طريقة امتداد (`extension method`). +لتحقيق الاتساق والوضوح، يفضَّل استخدام الطرق الأساسية (methods) بشكل مباشر بدلًا +من امتدادات `BuildContext`. وهذا مفيد أيضًا في الاختبارات، لأنه **لا يمكن** +إنشاء محاكاة (`mock`) لـ `extension method`. | الامتداد (extension) | الطريقة الصريحة (explicit method) | | -------------------- | ------------------------------------------------------------------- | @@ -37,7 +36,8 @@ import GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/ ## أمثلة (Examples) -**تجنب** استخدام امتدادات `BuildContext` للتفاعل مع نُسخ `Bloc` أو `Cubit`. +**تجنب** استخدام امتدادات `BuildContext` للتعامل مع نسخ (instances) من `Bloc` أو +`Cubit`. **مثال سيئ (BAD)**: @@ -49,7 +49,7 @@ import GoodSnippet from '~/components/lint-rules/avoid_build_context_extensions/ ## التفعيل (Enable) -لتفعيل قاعدة `avoid_build_context_extensions`، أضفها إلى ملف -`analysis_options.yaml` الخاص بك ضمن `bloc` > `rules`: +لتفعيل قاعدة `avoid_build_context_extensions`، أضفها إلى `analysis_options.yaml` +ضمن `bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/avoid_flutter_imports.mdx b/docs/src/content/docs/ar/lint-rules/avoid_flutter_imports.mdx index ee059acda15..bb680c774ea 100644 --- a/docs/src/content/docs/ar/lint-rules/avoid_flutter_imports.mdx +++ b/docs/src/content/docs/ar/lint-rules/avoid_flutter_imports.mdx @@ -1,6 +1,6 @@ --- title: تجنب استيراد Flutter (Avoid Flutter Imports) -description: قاعدة avoid_flutter_imports البرمجية الخاصة بـ Bloc. +description: قاعدة `avoid_flutter_imports` الخاصة بـ Bloc. --- import { Badge } from '@astrojs/starlight/components'; @@ -14,24 +14,27 @@ import GoodSnippet from '~/components/lint-rules/avoid_flutter_imports/GoodSnipp -تجنب إدخال تبعيات على Flutter ضمن مكونات منطق الأعمال (Business Logic -Components) (نُسخ `Bloc` أو `Cubit`). +تجنب إضافة تبعيات (dependencies) على Flutter داخل مكونات منطق الأعمال (Business +Logic Components) (نسخ (instances) من `Bloc` أو `Cubit`). -## الأساس المنطقي (Rationale) +## المبرر (Rationale) -يعد تقسيم التطبيق إلى طبقات (Layering an application) جزءًا أساسيًا من بناء -قاعدة أكواد قابلة للصيانة (maintainable codebase) ويساعد المطورين على التكرار -بسرعة وثقة. يجب أن يكون لكل طبقة مسؤولية واحدة (single responsibility) وأن تكون -قادرة على العمل والاختبار بشكل مستقل (in isolation). يتيح لك هذا احتواء -التغييرات في طبقات محددة، مما يقلل من تأثيرها على التطبيق بأكمله. +يعد تقسيم التطبيق إلى طبقات (Layering an application) جزءًا أساسيًا لبناء قاعدة +أكواد برمجية قابلة للصيانة (maintainable codebase)، كما يساعد المطورين على +التطوير بسرعة وثقة. يجب أن تمتلك كل طبقة مسؤولية واحدة (single responsibility) +وأن تكون قابلة للعمل والاختبار بشكل مستقل (in isolation). هذا يسهّل حصر +التغييرات داخل طبقات محددة ويقلل تأثيرها على التطبيق بالكامل. -ونتيجة لذلك، يجب أن تدير مكونات منطق الأعمال حالة الميزة (feature state) وأن -تكون مفصولة عن طبقة واجهة المستخدم (UI layer). يجب أن تتدفق الأحداث (Events) إلى -مكونات منطق الأعمال من طبقة واجهة المستخدم، ويجب أن تتدفق الحالة (State) من طبقة -منطق الأعمال إلى طبقة واجهة المستخدم. +بناءً على ذلك، من الأفضل أن تركز مكونات منطق الأعمال (Business Logic Components) +على إدارة حالة الميزة (`feature state`) فقط، مع بقائها مفصولة عن طبقة واجهة +المستخدم (UI layer). يجب أن تتدفق الأحداث (`events`) من طبقة واجهة المستخدم (UI +layer) إلى مكونات منطق الأعمال (Business Logic Components)، بينما تتدفق الحالة +(`state`) من طبقة منطق الأعمال (Business Logic layer) إلى طبقة واجهة المستخدم +(UI layer). -إن إبقاء مكونات منطق الأعمال مفصولة عن Flutter يوفر القدرة على إعادة استخدام -منطق الأعمال عبر منصات/أطر عمل متعددة (مثل Flutter، AngularDart، Jaspr، إلخ). +فصل مكونات منطق الأعمال (Business Logic Components) عن Flutter يسهّل إعادة +استخدام منطق الأعمال عبر منصات أو أطر عمل (frameworks) متعددة (مثل Flutter +وAngularDart وJaspr وغيرها). ## أمثلة (Examples) @@ -47,7 +50,7 @@ Components) (نُسخ `Bloc` أو `Cubit`). ## التفعيل (Enable) -لتفعيل قاعدة `avoid_flutter_imports`، أضفها إلى ملف `analysis_options.yaml` -الخاص بك ضمن `bloc` > `rules`: +لتفعيل قاعدة `avoid_flutter_imports`، أضفها إلى `analysis_options.yaml` ضمن +`bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/avoid_public_bloc_methods.mdx b/docs/src/content/docs/ar/lint-rules/avoid_public_bloc_methods.mdx index e5ccd5632d6..d782ee81f94 100644 --- a/docs/src/content/docs/ar/lint-rules/avoid_public_bloc_methods.mdx +++ b/docs/src/content/docs/ar/lint-rules/avoid_public_bloc_methods.mdx @@ -1,6 +1,6 @@ --- title: تجنب استخدام دوال Bloc العامة -description: قاعدة avoid_public_bloc_methods. +description: قاعدة `avoid_public_bloc_methods`. --- import { Badge } from '@astrojs/starlight/components'; @@ -14,32 +14,32 @@ import GoodSnippet from '~/components/lint-rules/avoid_public_bloc_methods/GoodS -تجنب كشف الدوال العامة (Public Methods) على كائنات `Bloc`. +تجنب كشف الدوال العامة (public methods) على instances من `Bloc`. -## الأساس المنطقي +## المبرر (Rationale) -تستجيب كائنات Bloc للأحداث الواردة (incoming events) وتُصدر حالات صادرة -(outgoing states). ونتيجة لذلك، فإن الطريقة الموصى بها للتواصل مع كائن Bloc هي -عبر الدالة `add`. في معظم الحالات، لا توجد حاجة لإنشاء تجريدات إضافية -(additional abstractions) فوق واجهة برمجة التطبيقات `add`. +يتفاعل الـ Bloc مع الأحداث الواردة (incoming events) ويُصدر الحالات الصادرة +(outgoing states). لذلك، الطريقة الموصى بها للتواصل مع instance من `Bloc` هي عبر +الدالة `add`. في معظم الحالات، لا حاجة لبناء تجريدات إضافية (additional +abstractions) فوق واجهة `add` (API). ![هيكلية Bloc](~/assets/concepts/bloc_architecture_full.png) -## أمثلة +## أمثلة (Examples) -**لا تقم** بكشف الدوال العامة على كائنات Bloc. +**لا تقم** بكشف الدوال العامة على instances من `Bloc`. -**مثال سيئ**: +**مثال سيئ (BAD)**: -**مثال جيد**: +**مثال جيد (GOOD)**: -## التفعيل +## التفعيل (Enable) -لتفعيل قاعدة `avoid_public_bloc_methods`، أضفها إلى ملف `analysis_options.yaml` -الخاص بك تحت المسار `bloc` > `rules`: +لتفعيل قاعدة `avoid_public_bloc_methods`، أضفها إلى `analysis_options.yaml` ضمن +`bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/avoid_public_fields.mdx b/docs/src/content/docs/ar/lint-rules/avoid_public_fields.mdx index 56c7741a823..78fffb18171 100644 --- a/docs/src/content/docs/ar/lint-rules/avoid_public_fields.mdx +++ b/docs/src/content/docs/ar/lint-rules/avoid_public_fields.mdx @@ -1,6 +1,6 @@ --- title: تجنب استخدام الحقول العامة -description: قاعدة avoid_public_fields. +description: قاعدة `avoid_public_fields`. --- import { Badge } from '@astrojs/starlight/components'; @@ -14,29 +14,29 @@ import GoodSnippet from '~/components/lint-rules/avoid_public_fields/GoodSnippet -تجنب كشف الحقول العامة (Public Fields) على كائنات `Bloc` و `Cubit`. +تجنب كشف الحقول العامة (public fields) على instances من `Bloc` و`Cubit`. -## الأساس المنطقي +## المبرر (Rationale) -تحافظ مكونات منطق الأعمال (Business logic components) على `state` الخاص بها -وتُصدر تغييرات الحالة عبر واجهة برمجة التطبيقات `emit`. ونتيجة لذلك، يجب كشف -جميع الحالات الظاهرة للعامة (public facing state) عبر كائن `state`. +تحافظ مكونات منطق الأعمال (Business Logic Components) على `state` الخاص بها +وتُصدر تغييرات الحالة عبر واجهة `emit` (API). لذلك، يجب كشف كل الحالة الظاهرة +للعامة (public-facing state) من خلال كائن `state`. -## أمثلة +## أمثلة (Examples) -**لا تقم** بكشف الحقول العامة على كائنات Bloc و Cubit. +**لا تقم** بكشف الحقول العامة على instances من `Bloc` و`Cubit`. -**مثال سيئ**: +**مثال سيئ (BAD)**: -**مثال جيد**: +**مثال جيد (GOOD)**: -## التفعيل +## التفعيل (Enable) -لتفعيل قاعدة `avoid_public_fields`، أضفها إلى ملف `analysis_options.yaml` الخاص -بك تحت المسار `bloc` > `rules`: +لتفعيل قاعدة `avoid_public_fields`، أضفها إلى `analysis_options.yaml` ضمن +`bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/prefer_bloc.mdx b/docs/src/content/docs/ar/lint-rules/prefer_bloc.mdx index c469eae1cda..e8ec01c1f67 100644 --- a/docs/src/content/docs/ar/lint-rules/prefer_bloc.mdx +++ b/docs/src/content/docs/ar/lint-rules/prefer_bloc.mdx @@ -1,6 +1,6 @@ --- title: تفضيل Bloc -description: قاعدة prefer_bloc. +description: قاعدة `prefer_bloc`. --- import { Badge } from '@astrojs/starlight/components'; @@ -13,35 +13,36 @@ import GoodSnippet from '~/components/lint-rules/prefer_bloc/GoodSnippet.astro'; -فضل استخدام كائنات `Bloc` على كائنات `Cubit`. +يفضَّل استخدام instances من `Bloc` بدلًا من instances من `Cubit`. -## الأساس المنطقي +## المبرر (Rationale) -هذه القاعدة هي قاعدة أسلوبية بحتة. في بعض الحالات، قد تفضل الفرق توحيد استخدام -كائنات `Bloc` فقط في جميع أنحاء تطبيقها من أجل الاتساق (consistency). +هذه قاعدة أسلوبية (stylistic rule) بحتة. في بعض الحالات، قد تفضّل الفرق توحيد +استخدام instances من `Bloc` فقط على مستوى التطبيق بالكامل لتحقيق الاتساق +(consistency). :::tip -تعرف على المزيد حول فوائد `Bloc` في +تعرّف على المزيد حول مزايا `Bloc` في [المفاهيم الأساسية](/ar/bloc-concepts/#مزايا-bloc). ::: -## أمثلة +## أمثلة (Examples) -**تجنب** استخدام كائنات `Cubit`. +**تجنب** استخدام instances من `Cubit`. -**مثال سيئ**: +**مثال سيئ (BAD)**: -**مثال جيد**: +**مثال جيد (GOOD)**: -## التفعيل +## التفعيل (Enable) -لتفعيل قاعدة `prefer_bloc`، أضفها إلى ملف `analysis_options.yaml` الخاص بك تحت -المسار `bloc` > `rules`: +لتفعيل قاعدة `prefer_bloc`، أضفها إلى `analysis_options.yaml` ضمن `bloc` > +`rules`: diff --git a/docs/src/content/docs/ar/lint-rules/prefer_build_context_extensions.mdx b/docs/src/content/docs/ar/lint-rules/prefer_build_context_extensions.mdx index 2d9524286bd..5522f77fa14 100644 --- a/docs/src/content/docs/ar/lint-rules/prefer_build_context_extensions.mdx +++ b/docs/src/content/docs/ar/lint-rules/prefer_build_context_extensions.mdx @@ -1,6 +1,6 @@ --- title: تفضيل استخدام امتدادات BuildContext -description: قاعدة prefer_build_context_extensions. +description: قاعدة `prefer_build_context_extensions`. --- import { Badge } from '@astrojs/starlight/components'; @@ -13,20 +13,21 @@ import GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions -فضل استخدام امتدادات `BuildContext` للوصول إلى كائن `Bloc` أو `Repository`. +يفضَّل استخدام امتدادات `BuildContext` للوصول إلى instance من `Bloc` أو +`Repository`. :::note -تم تقديم قاعدة التحقق هذه (lint rule) في الإصدار `0.3.2` من حزمة +تم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.2` من [`package:bloc_lint`](https://pub.dev/packages/bloc_lint). ::: -## الأساس المنطقي +## المبرر (Rationale) -من أجل الاتساق (consistency)، فضل استخدام امتدادات `BuildContext` مثل -`context.read` و `context.watch` و `context.select` بدلاً من `BlocProvider.of` و -`RepositoryProvider.of` و `BlocBuilder` أو `BlocSelector`. +لتحقيق الاتساق (consistency)، يفضَّل استخدام امتدادات `BuildContext` مثل +`context.read` و`context.watch` و`context.select` بدلًا من `BlocProvider.of` و +`RepositoryProvider.of` و`BlocBuilder` و`BlocSelector`. | الطريقة الصريحة (explicit method) | الامتداد (extension) | | ------------------------------------------------------------------- | --------------------- | @@ -34,21 +35,21 @@ import GoodSnippet from '~/components/lint-rules/prefer_build_context_extensions | `BlocBuilder(...)` أو `BlocProvider.of(context)` | `context.watch` | | `BlocSelector(...)` | `context.select` | -## أمثلة +## أمثلة (Examples) -**تجنب** استخدام `BlocProvider.of(context)` للوصول إلى كائن `Bloc`. +**تجنب** استخدام `BlocProvider.of(context)` للوصول إلى instance من `Bloc`. -**مثال سيئ**: +**مثال سيئ (BAD)**: -**مثال جيد**: +**مثال جيد (GOOD)**: -## التفعيل +## التفعيل (Enable) -لتفعيل قاعدة `prefer_build_context_extensions`، أضفها إلى ملف -`analysis_options.yaml` الخاص بك تحت المسار `bloc` > `rules`: +لتفعيل قاعدة `prefer_build_context_extensions`، أضفها إلى +`analysis_options.yaml` ضمن `bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/prefer_cubit.mdx b/docs/src/content/docs/ar/lint-rules/prefer_cubit.mdx index b8903e8de77..40b17d6cb19 100644 --- a/docs/src/content/docs/ar/lint-rules/prefer_cubit.mdx +++ b/docs/src/content/docs/ar/lint-rules/prefer_cubit.mdx @@ -1,6 +1,6 @@ --- title: تفضيل Cubit -description: قاعدة prefer_cubit. +description: قاعدة `prefer_cubit`. --- import { Badge } from '@astrojs/starlight/components'; @@ -13,23 +13,24 @@ import GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro' -**تفضيل** استخدام كائنات (`Cubit`) بدلاً من كائنات (`Bloc`). +يفضَّل استخدام instances من `Cubit` بدلًا من instances من `Bloc`. -## الأساس المنطقي (Rationale) +## المبرر (Rationale) -هذه القاعدة هي قاعدة أسلوبية بحتة. في بعض الحالات، قد تفضل الفرق توحيد استخدام -كائنات `Cubit` فقط في جميع أنحاء تطبيقها بالكامل لضمان الاتساق. +هذه قاعدة أسلوبية (stylistic rule) بحتة. في بعض الحالات، قد تفضّل الفرق توحيد +استخدام instances من `Cubit` فقط على مستوى التطبيق بالكامل لتحقيق الاتساق +(consistency). :::tip تعرّف على المزيد حول مزايا `Cubit` في -[المفاهيم الأساسية (Core Concepts)](/ar/bloc-concepts/#مزايا-cubit). +[المفاهيم الأساسية](/ar/bloc-concepts/#مزايا-cubit). ::: -## الأمثلة (Examples) +## أمثلة (Examples) -**تجنب** استخدام كائنات `Bloc`. +**تجنب** استخدام instances من `Bloc`. **مثال سيئ (BAD)**: @@ -41,7 +42,7 @@ import GoodSnippet from '~/components/lint-rules/prefer_cubit/GoodSnippet.astro' ## التفعيل (Enable) -لتفعيل قاعدة `prefer_cubit`، أضفها إلى ملف `analysis_options.yaml` الخاص بك ضمن -قسم `bloc` > `rules`: +لتفعيل قاعدة `prefer_cubit`، أضفها إلى `analysis_options.yaml` ضمن `bloc` > +`rules`: diff --git a/docs/src/content/docs/ar/lint-rules/prefer_file_naming_conventions.mdx b/docs/src/content/docs/ar/lint-rules/prefer_file_naming_conventions.mdx index f768bd6a080..cb2715c38f7 100644 --- a/docs/src/content/docs/ar/lint-rules/prefer_file_naming_conventions.mdx +++ b/docs/src/content/docs/ar/lint-rules/prefer_file_naming_conventions.mdx @@ -1,6 +1,6 @@ --- title: تفضيل اصطلاحات تسمية الملفات -description: قاعدة prefer_file_naming_conventions. +description: قاعدة `prefer_file_naming_conventions`. --- import { Badge } from '@astrojs/starlight/components'; @@ -14,32 +14,32 @@ import GoodSnippet from '~/components/lint-rules/prefer_file_naming_conventions/ -**تفضيل** اتباع اصطلاحات تسمية الملفات. +يفضَّل اتباع اصطلاحات تسمية الملفات. :::note -تم تقديم قاعدة التحليل (lint rule) هذه في الإصدار `0.3.0` من حزمة +تم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.3.0` من حزمة [`package:bloc_lint`](https://pub.dev/packages/bloc_lint) ::: -## الأساس المنطقي (Rationale) +## المبرر (Rationale) -من أجل الاتساق، وسهولة الصيانة، وفصل الاهتمامات (separation of concerns)، -**يُفضل** تعريف كائنات `bloc` و `cubit` في ملفات Dart الخاصة بها بدلاً من -تضمينها مباشرة (inlining). +لتحقيق الاتساق وسهولة الصيانة وفصل الاهتمامات (separation of concerns)، يفضَّل +تعريف instances من `bloc` و`cubit` في ملفات Dart الخاصة بها بدلًا من تضمينها +مباشرة (inlining). :::tip فكر في استخدام الأمر `bloc new ` من حزمة -[package:bloc_tools](https://pub.dev/packages/bloc_tools) لإنشاء كائنات -bloc/cubit جديدة بسرعة وبشكل متسق. +[package:bloc_tools](https://pub.dev/packages/bloc_tools) لإنشاء instances جديدة +من bloc/cubit بسرعة وبشكل متسق. ::: -## الأمثلة (Examples) +## أمثلة (Examples) -**يُفضل** التصريح عن كائنات bloc/cubit في ملفاتها الخاصة. +**يفضَّل** التصريح عن instances من bloc/cubit في ملفاتها الخاصة. **مثال جيد (GOOD)**: @@ -52,6 +52,6 @@ bloc/cubit جديدة بسرعة وبشكل متسق. ## التفعيل (Enable) لتفعيل قاعدة `prefer_file_naming_conventions`، أضفها إلى ملف -`analysis_options.yaml` الخاص بك ضمن قسم `bloc` > `rules`: +`analysis_options.yaml` ضمن `bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint-rules/prefer_void_public_cubit_methods.mdx b/docs/src/content/docs/ar/lint-rules/prefer_void_public_cubit_methods.mdx index d1968cc523a..0428bace952 100644 --- a/docs/src/content/docs/ar/lint-rules/prefer_void_public_cubit_methods.mdx +++ b/docs/src/content/docs/ar/lint-rules/prefer_void_public_cubit_methods.mdx @@ -1,6 +1,6 @@ --- title: تفضيل الدوال العامة ذات القيمة المرتجعة Void في Cubit -description: قاعدة prefer_void_public_cubit_methods. +description: قاعدة `prefer_void_public_cubit_methods`. --- import { Badge } from '@astrojs/starlight/components'; @@ -14,24 +14,25 @@ import GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_method -**تفضيل** استخدام الدوال العامة ذات القيمة المرتجعة `void` في كائنات `Cubit`. +يفضَّل أن تكون الدوال العامة (public methods) في instances من `Cubit` ذات قيمة +مرتجعة `void`. :::note -تم تقديم قاعدة التحليل (lint rule) هذه في الإصدار `0.2.0-dev.2` من حزمة +تم تقديم قاعدة التدقيق (lint rule) هذه في الإصدار `0.2.0-dev.2` من حزمة [`package:bloc_lint`](https://pub.dev/packages/bloc_lint) ::: -## الأساس المنطقي (Rationale) +## المبرر (Rationale) -يجب استخدام الدوال العامة في كائنات `Cubit` لإخطار `Cubit` وبدء تغييرات الحالة -عبر دالة `emit`. إذا احتاج المُستدعي (caller) إلى الوصول إلى أي معلومات عن -الحالة، فيجب عليه الوصول إليها من خلال خاصية `state` بدلاً من ذلك. +يجب استخدام الدوال العامة (public methods) في instances من `Cubit` لإخطار +`Cubit` وبدء تغييرات الحالة عبر الدالة `emit`. وإذا احتاج المستدعي (caller) إلى +أي معلومات عن الحالة، فيفترض أن يحصل عليها من `state` بدلًا من ذلك. :::note -القواعد التالية ذات صلة ويتم تفعيلها عادةً بالتزامن مع قاعدة +القواعد التالية مرتبطة، وغالبًا ما تُفعّل مع قاعدة `prefer_void_public_cubit_methods`: - [`avoid_public_bloc_methods`](/ar/lint-rules/avoid_public_bloc_methods) @@ -39,9 +40,10 @@ import GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_method ::: -## الأمثلة (Examples) +## أمثلة (Examples) -**تجنب** الدوال العامة ذات القيمة المرتجعة غير `void` في كائنات `Cubit`. +**تجنب** الدوال العامة (public methods) ذات القيمة المرتجعة غير `void` في +instances من `Cubit`. **مثال سيئ (BAD)**: @@ -54,6 +56,6 @@ import GoodSnippet from '~/components/lint-rules/prefer_void_public_cubit_method ## التفعيل (Enable) لتفعيل قاعدة `prefer_void_public_cubit_methods`، أضفها إلى ملف -`analysis_options.yaml` الخاص بك ضمن قسم `bloc` > `rules`: +`analysis_options.yaml` ضمن `bloc` > `rules`: diff --git a/docs/src/content/docs/ar/lint/configuration.mdx b/docs/src/content/docs/ar/lint/configuration.mdx index c0abbce3376..947cc244ef3 100644 --- a/docs/src/content/docs/ar/lint/configuration.mdx +++ b/docs/src/content/docs/ar/lint/configuration.mdx @@ -1,6 +1,6 @@ --- -title: إعدادات المدقق (Linter Configuration) -description: إعداد مدقق bloc (bloc linter). +title: إعداد أداة التدقيق (Linter Configuration) +description: كيفية إعداد أداة التدقيق الخاصة بـ bloc. sidebar: order: 3 --- @@ -13,42 +13,42 @@ import AvoidFlutterImportsWarningSnippet from '~/components/lint/ImportFlutterWa import RunBlocLintCounterCubitSnippet from '~/components/lint/RunBlocLintCounterCubitSnippet.astro'; import AvoidFlutterImportsWarningOutputSnippet from '~/components/lint/ImportFlutterWarningOutputSnippet.astro'; -بشكل افتراضي، لن يقوم مدقق bloc بالإبلاغ عن أي تشخيصات (diagnostics) ما لم تقم -**بإعداد خيارات التحليل (analysis options) للمشروع بشكل صريح**. +افتراضيًا، لن تعرض أداة التدقيق في bloc أي تشخيصات ما لم تُعدّ خيارات التحليل +(analysis options) في المشروع بشكل صريح. -للبدء، قم بإنشاء أو تعديل ملف `analysis_options.yaml` الموجود في **الجذر الرئيسي -لمشروعك**، بحيث يتضمن قائمة بالقواعد تحت المفتاح الرئيسي `bloc`: +للبدء، أنشئ ملف `analysis_options.yaml` أو عدّله في جذر المشروع، بحيث يحتوي على +قائمة القواعد تحت المفتاح الأعلى `bloc`: -قم بتشغيل المدقق باستخدام الأمر التالي في الطرفية (terminal): +شغّل أداة التدقيق باستخدام الأمر التالي في terminal: -سيقوم الأمر أعلاه بتحليل جميع الملفات في المجلد الحالي والمجلدات الفرعية التابعة -له، ولكن يمكنك أيضًا فحص ملفات ومجلدات محددة عن طريق تمريرها كمعاملات للأمر -(command-line arguments): +سيحلّل الأمر أعلاه جميع الملفات في المجلد الحالي ومجلداته الفرعية. ويمكنك أيضًا +تدقيق ملفات أو مجلدات محددة عبر تمريرها كوسائط لسطر الأوامر (command-line +arguments): -سيقوم الأمر أعلاه بتحليل جميع الأكواد البرمجية في مجلدات `src` و `test`. +سيحلّل الأمر أعلاه كل الأكواد البرمجية داخل مجلدي `src` و`test`. -إذا كانت قاعدة `avoid_flutter_imports` مُفعلة، فسيتم الإبلاغ عن أي ملف `bloc` أو -`cubit` يحتوي على استيراد (import) لـ Flutter كتحذير: +إذا كانت قاعدة `avoid_flutter_imports` مفعّلة، فسيُبلّغ عن أي ملف `bloc` أو +`cubit` يحتوي على `import` لـ Flutter على أنه تحذير: -يمكنك رؤية التحذير عن طريق تشغيل الأمر `bloc lint`: +يمكنك مشاهدة التحذير عبر تشغيل الأمر `bloc lint`: -يجب أن يبدو الإخراج (output) كما يلي: +يجب أن يكون المخرج (output) بالشكل التالي: :::note -فيما يلي جميع قواعد المدقق (lint rules) المدعومة: +فيما يلي جميع قواعد التدقيق المدعومة (lint rules): -ثم قم بتعديل ملف `analysis_options.yaml` الخاص بك لتضمين مجموعة القواعد: +ثم عدّل ملف `analysis_options.yaml` لإضافة مجموعة القواعد: :::note -عند نشر إصدار جديد من `bloc_lint`، قد تبدأ الأكواد التي اجتازت التحليل الساكن -سابقًا بالفشل. نوصي بتحديث الكود الخاص بك ليتوافق مع القواعد الجديدة، أو يمكنك -أيضًا تمكين أو تعطيل القواعد الفردية اختياريًا. +عند إصدار نسخة جديدة من `bloc_lint`، قد تبدأ الأكواد البرمجية التي كانت تجتاز +التحليل الساكن سابقًا في الفشل. نوصي بتحديث الأكواد البرمجية لتتوافق مع القواعد +الجديدة، أو تمكين/تعطيل القواعد الفردية حسب الحاجة. ::: ### تمكين القواعد الفردية -لتمكين القواعد الفردية، أضف `bloc:` إلى ملف `analysis_options.yaml` كمفتاح رئيسي -(top-level key) و `rules:` كمفتاح من المستوى الثاني. في الأسطر اللاحقة، حدد -القواعد التي تريدها كقائمة YAML (مسبوقة بشرطات). +لتمكين قواعد فردية، أضف `bloc:` إلى ملف `analysis_options.yaml` كمفتاح من +المستوى الأعلى (`top-level key`)، ثم أضف `rules:` كمفتاح من المستوى الثاني. بعد +ذلك، اكتب القواعد المطلوبة على شكل قائمة YAML (مسبوقة بعلامة الشرطة `-`). على سبيل المثال: @@ -69,89 +67,88 @@ dependency): ### تعطيل القواعد الفردية -إذا قمت بتضمين مجموعة قواعد موجودة مثل المجموعة **الموصى بها (recommended)**، -فقد ترغب في تعطيل قاعدة أو أكثر من قواعد التدقيق المضمنة. تعطيل القواعد مشابه -لتمكينها، ولكنه يتطلب استخدام خريطة YAML (YAML map) بدلاً من قائمة. +إذا كنت تضمّن مجموعة قواعد موجودة مسبقًا، مثل `recommended`، فقد تحتاج إلى تعطيل +قاعدة واحدة أو أكثر من القواعد المضمّنة. آلية التعطيل مشابهة للتمكين، لكنها +تتطلب استخدام YAML map بدلًا من قائمة. -على سبيل المثال، يشتمل ما يلي على مجموعة قواعد التدقيق الموصى بها باستثناء -`avoid_public_bloc_methods`، وبالإضافة إلى ذلك يقوم بتمكين قاعدة `prefer_bloc`: +على سبيل المثال، الإعداد التالي يستخدم مجموعة القواعد الموصى بها مع تعطيل +`avoid_public_bloc_methods`، ويُفعّل أيضًا قاعدة `prefer_bloc`: ## تخصيص شدة القاعدة -يمكنك تعديل شدة أي قاعدة على النحو التالي: +يمكنك تغيير شدة أي قاعدة بالشكل التالي: -الآن سيتم الإبلاغ عن نفس قاعدة التدقيق بشدة `info` بدلاً من `warning`: +الآن سيتم الإبلاغ عن القاعدة نفسها بمستوى `info` بدلًا من `warning`: -يجب أن يبدو إخراج أمر `bloc lint` كما يلي: +يجب أن تبدو المخرجات (output) لأمر `bloc lint` كما يلي: -خيارات الشدة المدعومة هي: +مستويات الشدة المدعومة: -| الشدة (Severity) | الوصف (Description) | -| :--------------- | :------------------------------------------- | -| `error` | يشير إلى أن النمط غير مسموح به. | -| `warning` | يشير إلى أن النمط مشبوه ولكنه مسموح به. | -| `info` | يوفر معلومات للمستخدمين ولكنه لا يمثل مشكلة. | -| `hint` | يقترح طريقة أفضل لتحقيق النتيجة. | +| الشدة (`Severity`) | الوصف (`Description`) | +| :----------------- | :----------------------------------------- | +| `error` | يشير إلى أن هذا النمط غير مسموح. | +| `warning` | يشير إلى أن النمط مريب لكنه مسموح. | +| `info` | يقدّم معلومات للمستخدم، لكنه لا يعد مشكلة. | +| `hint` | يقترح طريقة أفضل للوصول إلى النتيجة. | ## استثناء الملفات -في بعض الأحيان يكون من المقبول أن يفشل التحليل الساكن. على سبيل المثال، قد ترغب -في تجاهل التحذيرات أو الأخطاء التي يتم الإبلاغ عنها في الكود الذي تم إنشاؤه -(generated code) ولم تكتبه أنت وفريقك. تمامًا كما هو الحال مع قواعد تدقيق Dart -الرسمية، يمكنك استخدام خيار المحلل `exclude:` لاستثناء الملفات من التحليل -الساكن. +أحيانًا يكون من المقبول تجاهل فشل التحليل الساكن. على سبيل المثال، قد ترغب في +تجاهل التحذيرات أو الأخطاء في الأكواد البرمجية المُولّدة (`generated code`) التي +لم يكتبها فريقك. وكما في قواعد تدقيق Dart الرسمية، يمكنك استخدام خيار المحلل +`exclude:` لاستثناء ملفات من التحليل الساكن. -يمكنك إما سرد الملفات الفردية أو استخدام أنماط +يمكنك إما تحديد ملفات بعينها أو استخدام أنماط [`glob`](https://pub.dev/packages/glob). :::note -يجب أن يكون كل استخدام لأنماط glob نسبيًا للمجلد الذي يحتوي على ملف +يجب أن تكون جميع أنماط `glob` نسبيةً إلى المجلد الذي يحتوي على ملف `analysis_options.yaml` المقابل. ::: -على سبيل المثال، يمكننا استثناء جميع أكواد Dart التي تم إنشاؤها عبر خيارات -التحليل التالية: +على سبيل المثال، يمكننا استثناء جميع أكواد Dart المُولّدة عبر إعدادات التحليل +التالية: ## تجاهل القواعد -تمامًا كما هو الحال مع قواعد تدقيق Dart الرسمية، يمكنك تجاهل قواعد تدقيق bloc -لملف معين أو سطر من الكود باستخدام `// ignore_for_file` و `// ignore` على +تمامًا كما هو الحال مع قواعد تدقيق Dart الرسمية، يمكنك تجاهل قواعد bloc لملف +معيّن أو سطر من الأكواد البرمجية باستخدام `// ignore_for_file` و `// ignore` على التوالي. :::note -لتجاهل قواعد متعددة لسطر أو ملف معين، قم بتوفير قائمة مفصولة بفواصل. +لتجاهل عدة قواعد في سطر أو ملف معيّن، استخدم قائمة مفصولة بفواصل. ::: ### تجاهل الأسطر -يمكننا تجاهل حالات محددة لانتهاكات القواعد عن طريق إضافة تعليق `ignore` إما فوق -السطر المخالف مباشرة أو بإلحاقه بالسطر المخالف. +يمكنك تجاهل حالات محددة من انتهاكات القواعد بإضافة تعليق `ignore` إما فوق السطر +المخالف مباشرة، أو في نهاية السطر نفسه. -على سبيل المثال، يمكننا تجاهل حالات محددة من `prefer_file_naming_conventions` في -ملف معين: +على سبيل المثال، يمكن تجاهل حالات محددة من `prefer_file_naming_conventions` في +ملف معيّن: ### تجاهل الملفات -يمكننا تجاهل جميع حالات انتهاكات القواعد داخل ملف عن طريق إضافة تعليق +يمكنك تجاهل جميع حالات انتهاكات القواعد داخل ملف عبر إضافة تعليق `ignore_for_file` في أي مكان في الملف. -على سبيل المثال، يمكننا تجاهل جميع حالات `prefer_file_naming_conventions` في ملف -معين: +على سبيل المثال، يمكن تجاهل جميع حالات `prefer_file_naming_conventions` في ملف +معيّن: diff --git a/docs/src/content/docs/ar/lint/index.mdx b/docs/src/content/docs/ar/lint/index.mdx index a68f6e535f1..c6e7d15ed1f 100644 --- a/docs/src/content/docs/ar/lint/index.mdx +++ b/docs/src/content/docs/ar/lint/index.mdx @@ -1,6 +1,6 @@ --- -title: نظرة عامة على المدقق (Linter Overview) -description: مقدمة لمدقق bloc. +title: نظرة عامة على أداة التدقيق (Linter Overview) +description: مقدمة إلى أداة التدقيق الخاصة بـ bloc. sidebar: order: 1 --- @@ -12,57 +12,55 @@ import InstallBlocLintSnippet from '~/components/lint/InstallBlocLintSnippet.ast import BlocLintRecommendedAnalysisOptionsSnippet from '~/components/lint/BlocLintRecommendedAnalysisOptionsSnippet.astro'; import RunBlocLintInCurrentDirectorySnippet from '~/components/lint/RunBlocLintInCurrentDirectorySnippet.astro'; -التدقيق (Linting) هو عملية **التحليل الساكن (statically analyzing)** للكود للبحث -عن الأخطاء المحتملة بالإضافة إلى الأخطاء البرمجية والأسلوبية. +التدقيق (Linting) هو عملية تحليل ساكن للكود لاكتشاف الأخطاء المحتملة، إلى جانب +المشكلات البرمجية ومشكلات الأسلوب. -يحتوي Bloc على مدقق مدمج (built-in linter)، يمكن استخدامه من خلال بيئة التطوير -المتكاملة (IDE) الخاصة بك أو عبر -[`أدوات سطر أوامر bloc`](https://pub.dev/packages/bloc_tools) باستخدام الأمر -`bloc lint`. +توفّر مكتبة Bloc أداة تدقيق مدمجة يمكن استخدامها عبر بيئة التطوير (IDE)، أو من +خلال [`أدوات سطر أوامر bloc`](https://pub.dev/packages/bloc_tools) باستخدام +الأمر `bloc lint`. -بمساعدة مدقق bloc، يمكنك تحسين جودة قاعدة الكود الخاصة بك وفرض الاتساق دون -الحاجة إلى تنفيذ سطر واحد من الكود. +بمساعدة أداة التدقيق في bloc، يمكنك رفع جودة قاعدة الأكواد البرمجية وفرض الاتساق +دون تنفيذ أي سطر من الكود. -على سبيل المثال، ربما قمت عن طريق الخطأ باستيراد اعتمادية (dependency) خاصة بـ -Flutter إلى الـ cubit الخاص بك: +على سبيل المثال، قد تستورد بالخطأ اعتمادًا خاصًا بـ Flutter داخل الـ cubit: -إذا تم إعداده بشكل صحيح، سيشير مدقق bloc إلى الاستيراد وينتج التحذير التالي: +إذا كانت الأداة مُعدّة بشكل صحيح، فستشير إلى هذا الاستيراد وتُظهر التحذير +التالي: -في الأقسام التالية، سنتناول كيفية تثبيت وإعداد وتخصيص مدقق bloc حتى تتمكن من -الاستفادة من التحليل الساكن في قاعدة الكود الخاصة بك. +في الأقسام التالية، سنشرح كيفية تثبيت أداة التدقيق في bloc وإعدادها وتخصيصها، +حتى تستفيد من التحليل الساكن في مشروعك. ## البدء السريع (Quick Start) -ابدأ باستخدام مدقق bloc في بضع خطوات سريعة وسهلة. +ابدأ باستخدام أداة التدقيق في bloc عبر خطوات سريعة وبسيطة. :::note -لكي تبدأ باستخدام bloc، يجب أن يكون لديك [Dart SDK](https://dart.dev/get-dart) -مثبتًا على جهازك. +لبدء استخدام bloc، يجب أن تكون [Dart SDK](https://dart.dev/get-dart) مثبتة على +جهازك. ::: -1. قم بتثبيت [أدوات سطر أوامر bloc](https://pub.dev/packages/bloc_tools) +1. ثبّت [أدوات سطر أوامر bloc](https://pub.dev/packages/bloc_tools) -2. قم بتثبيت حزمة [bloc_lint](https://pub.dev/packages/bloc_lint) +2. ثبّت حزمة [bloc_lint](https://pub.dev/packages/bloc_lint) -3. أضف ملف `analysis_options.yaml` إلى الجذر الرئيسي لمشروعك مع القواعد الموصى - بها +3. أضف ملف `analysis_options.yaml` إلى جذر مشروعك مع القواعد الموصى بها -4. قم بتشغيل المدقق +4. شغّل أداة التدقيق -هذا كل ما في الأمر 🎉 +وهذا كل ما تحتاج إليه 🎉 -استمر في القراءة للحصول على نظرة أكثر تعمقًا حول إعداد وتخصيص مدقق bloc. +تابع القراءة للحصول على شرح أعمق لإعداد أداة التدقيق في bloc وتخصيصها. diff --git a/docs/src/content/docs/ar/lint/installation.mdx b/docs/src/content/docs/ar/lint/installation.mdx index d65df14bf9a..d47c8f94146 100644 --- a/docs/src/content/docs/ar/lint/installation.mdx +++ b/docs/src/content/docs/ar/lint/installation.mdx @@ -1,6 +1,6 @@ --- -title: تثبيت المدقق (Linter Installation) -description: تثبيت مدقق bloc. +title: تثبيت أداة التدقيق (Linter Installation) +description: كيفية تثبيت أداة التدقيق الخاصة بـ bloc. sidebar: order: 2 --- @@ -15,46 +15,45 @@ import BlocLintMultipleRecommendedAnalysisOptionsSnippet from '~/components/lint ## أدوات سطر الأوامر -لاستخدام المدقق من سطر الأوامر، قم بتثبيت حزمة +لاستخدام أداة التدقيق من سطر الأوامر، ثبّت حزمة [`package:bloc_tools`](https://pub.dev/packages/bloc_tools) عبر الأمر التالي: -بمجرد تثبيت أدوات سطر أوامر bloc، يمكنك تشغيل مدقق bloc عبر الأمر `bloc lint`: +بعد تثبيت أدوات سطر أوامر bloc، يمكنك تشغيل أداة التدقيق عبر الأمر `bloc lint`: ## مجموعة القواعد الموصى بها -لتثبيت مجموعة قواعد التدقيق الموصى بها، قم بتثبيت حزمة -[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) كاعتماد تطوير (dev -dependency) عبر الأمر التالي: +لتثبيت مجموعة قواعد التدقيق الموصى بها، ثبّت حزمة +[`package:bloc_lint`](https://pub.dev/packages/bloc_lint) كاعتماد تطوير +(`dev dependency`) باستخدام الأمر التالي: -بعد ذلك، أضف ملف `analysis_options.yaml` إلى الجذر الرئيسي لمشروعك مع مجموعة -القواعد الموصى بها: +ثم أضف ملف `analysis_options.yaml` إلى جذر المشروع مع مجموعة القواعد الموصى بها: -إذا لزم الأمر، يمكنك تضمين مجموعات قواعد متعددة عن طريق تعريفها كقائمة: +عند الحاجة، يمكنك تضمين أكثر من مجموعة قواعد عبر تعريفها كقائمة: ## تكاملات بيئات التطوير المتكاملة (IDE Integrations) -تدعم بيئات التطوير المتكاملة (IDEs) التالية رسميًا مدقق bloc وخادم اللغة -(language server) لتوفير تشخيصات فورية مباشرة داخل بيئة التطوير الخاصة بك. +تدعم بيئات التطوير المتكاملة (IDEs) التالية رسميًا أداة التدقيق في bloc وخادم +اللغة (language server) لتوفير تشخيصات فورية مباشرة داخل بيئة التطوير الخاصة بك. - يتوفر دعم [إضافة Bloc + يتوفر دعم [إضافة Bloc لـ VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) - في الإصدار v6.8.0. + ابتداءً من الإصدار `v6.8.0`. - يتوفر دعم [إضافة Bloc - IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) في الإصدار - v4.1.0. + يتوفر دعم [إضافة Bloc لـ + IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc) ابتداءً من + الإصدار `v4.1.0`. diff --git a/docs/src/content/docs/ar/migration.mdx b/docs/src/content/docs/ar/migration.mdx index a3f45fc7437..528964e0243 100644 --- a/docs/src/content/docs/ar/migration.mdx +++ b/docs/src/content/docs/ar/migration.mdx @@ -1,14 +1,14 @@ --- -title: دليل الانتقال (Migration Guide) -description: انتقل إلى أحدث إصدار مستقر من Bloc. +title: دليل الترقية (Migration Guide) +description: الترقية إلى أحدث إصدار مستقر من Bloc. --- import { Code, Tabs, TabItem } from '@astrojs/starlight/components'; :::tip -يرجى الرجوع إلى [سجل الإصدارات](https://github.com/felangel/bloc/releases) لمزيد -من المعلومات حول التغييرات في كل إصدار. +يرجى الرجوع إلى [سجل الإصدارات](https://github.com/felangel/bloc/releases) +للاطلاع على تفاصيل التغييرات في كل إصدار. ::: @@ -80,49 +80,48 @@ void main() async { ### `package:bloc` -#### ❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة +#### ❗🧹 إزالة الواجهات المهجورة :::note[ما الذي تغير؟] -في إصدار bloc v9.0.0، تمت إزالة جميع واجهات برمجة التطبيقات التي تم وضع علامة -"مهجورة" (deprecated) عليها سابقاً. +في الإصدار bloc v9.0.0، تمت إزالة جميع الواجهات التي تم الإعلان عنها سابقًا كـ +"مهجورة" (deprecated). ::: ##### ملخص -- تمت إزالة `BlocOverrides` لصالح `Bloc.observer` و `Bloc.transformer`. +- تمت إزالة `BlocOverrides` واستبداله بـ `Bloc.observer` و `Bloc.transformer`. -#### ❗✨ تقديم واجهة `EmittableStateStreamableSource` الجديدة +#### ❗✨ تقديم واجهة `EmittableStateStreamableSource` :::note[ما الذي تغير؟] -في إصدار bloc v9.0.0، تم تقديم واجهة أساسية جديدة تسمى +في هذا الإصدار، تم تقديم واجهة أساسية جديدة باسم `EmittableStateStreamableSource`. ::: -##### الأسباب (Rationale) +##### السبب -كانت `package:bloc_test` سابقاً مرتبطة بشكل وثيق بـ `BlocBase`. تم تقديم واجهة -`EmittableStateStreamableSource` للسماح بفصل `blocTest` عن التطبيق الفعلي لـ -`BlocBase`. +كان `package:bloc_test` مرتبطًا بشكل مباشر بـ `BlocBase`. تم تقديم هذه الواجهة +لفصل `blocTest` عن التنفيذ الفعلي لـ `BlocBase`. ### `package:hydrated_bloc` -#### ✨ إعادة تقديم API الـ `HydratedBloc.storage` +#### ✨ إعادة تقديم `HydratedBloc.storage` :::note[ما الذي تغير؟] -في إصدار hydrated_bloc v9.0.0، تمت إزالة `HydratedBlocOverrides` لصالح API الـ -`HydratedBloc.storage`. +في الإصدار hydrated_bloc v9.0.0، تمت إزالة `HydratedBlocOverrides` واستبداله +باستخدام `HydratedBloc.storage`. ::: -##### الأسباب (Rationale) +##### السبب -راجع -[الأسباب الكامنة وراء إعادة تقديم واجهات Bloc.observer و Bloc.transformer](/ar/migration/#-إعادة-تقديم-واجهات-برمجة-التطبيقات-blocobserver-و-bloctransformer). +راجع: +[سبب إعادة تقديم Bloc.observer و Bloc.transformer](/ar/migration/#-إعادة-تقديم-واجهات-برمجة-التطبيقات-blocobserver-و-bloctransformer) **v8.x.x** @@ -198,27 +197,28 @@ void main() { } ``` -الكود أعلاه، رغم أنه يبدو غير ضار، يمكن أن يؤدي في الواقع إلى العديد من الأخطاء -التي يصعب تتبعها. فأي Zone يتم استدعاء `WidgetsFlutterBinding.ensureInitialized` -منها في البداية ستكون هي الـ Zone التي يتم فيها التعامل مع أحداث الإيماءات (مثل -استدعاءات `onTap` و `onPressed`) بسبب `GestureBinding.initInstances`. هذه مجرد -واحدة من العديد من المشاكل الناجمة عن استخدام `zoneValues`. +الكود أعلاه قد يبدو بسيطًا وغير ضار، لكنه قد يؤدي فعليًا إلى أخطاء يصعب تتبعها. -بالإضافة إلى ذلك، يقوم Flutter بالعديد من الأشياء خلف الكواليس والتي تتضمن -تفريع/تعديل الـ Zones (خاصة عند تشغيل الاختبارات) مما قد يؤدي إلى سلوكيات غير -متوقعة (وفي كثير من الحالات سلوكيات خارجة عن سيطرة المطور -- انظر المشاكل -أدناه). +الـ Zone التي يتم استدعاء `WidgetsFlutterBinding.ensureInitialized` منها في +البداية ستكون هي نفسها المستخدمة لمعالجة أحداث الواجهة (مثل `onTap` و +`onPressed`) وذلك بسبب `GestureBinding.initInstances`. + +وهذا مجرد مثال واحد من العديد من المشاكل الناتجة عن استخدام `zoneValues`. + +بالإضافة إلى ذلك، يقوم Flutter بالعديد من العمليات خلف الكواليس التي تتضمن إنشاء +أو تعديل الـ Zones (خصوصًا أثناء تشغيل الاختبارات)، مما قد يؤدي إلى سلوك غير +متوقع، وفي بعض الحالات سلوك خارج عن سيطرة المطور (انظر المشاكل أدناه). بسبب استخدام [runZoned](https://api.flutter.dev/flutter/dart-async/runZoned.html)، أدى -الانتقال إلى API الـ `BlocOverrides` إلى اكتشاف العديد من الأخطاء/القيود في -Flutter (تحديداً حول اختبارات الـ Widget والـ Integration): +الانتقال إلى `BlocOverrides` إلى اكتشاف عدة مشاكل وقيود في Flutter، خاصة في +اختبارات Widget و Integration: - https://github.com/flutter/flutter/issues/96939 - https://github.com/flutter/flutter/issues/94123 - https://github.com/flutter/flutter/issues/93676 -والتي أثرت على العديد من المطورين الذين يستخدمون مكتبة bloc: +كما أثرت هذه المشاكل على العديد من مستخدمي مكتبة bloc: - https://github.com/felangel/bloc/issues/3394 - https://github.com/felangel/bloc/issues/3350 @@ -253,35 +253,33 @@ void main() { ### `package:bloc` -#### ❗✨ تقديم API الـ `BlocOverrides` الجديد +#### ❗✨ تقديم `BlocOverrides` :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، تمت إزالة `Bloc.observer` و `Bloc.transformer` لصالح API -الـ `BlocOverrides`. +في الإصدار bloc v8.0.0، تمت إزالة `Bloc.observer` و `Bloc.transformer` +واستبدالهما بـ `BlocOverrides`. ::: -##### الأسباب (Rationale) +##### السبب -كان API السابق المستخدم لتجاوز الـ `BlocObserver` والـ `EventTransformer` -الافتراضيين يعتمد على "singleton" عالمي لكل منهما. +كان النهج السابق لتجاوز `BlocObserver` و `EventTransformer` يعتمد على singleton +عام لكل منهما. ونتيجة لذلك، لم يكن من الممكن: -- امتلاك تطبيقات متعددة لـ `BlocObserver` أو `EventTransformer` محصورة في أجزاء - مختلفة من التطبيق. -- جعل تجاوزات `BlocObserver` أو `EventTransformer` محصورة في حزمة (package) - معينة. - - إذا كانت الحزمة تعتمد على `package:bloc` وسجلت الـ `BlocObserver` الخاص بها، - فسيتعين على أي مستخدم للحزمة إما الكتابة فوق الـ `BlocObserver` الخاص - بالحزمة أو إرسال التقارير إليه. +- استخدام عدة تطبيقات مختلفة من `BlocObserver` أو `EventTransformer` ضمن أجزاء + مختلفة من التطبيق +- حصر (scoping) هذه الإعدادات داخل حزمة (package) معينة + - في حال قامت حزمة تعتمد على `package:bloc` بتسجيل `BlocObserver` خاص بها، + سيُجبر المستخدم إما على استبداله أو التعامل معه كما هو -كما كان من الصعب أيضاً إجراء الاختبارات بسبب الحالة العالمية المشتركة عبر +كما أن هذا النهج جعل عملية الاختبار أكثر صعوبة بسبب وجود حالة عامة مشتركة بين الاختبارات. -يقدم Bloc v8.0.0 فئة `BlocOverrides` التي تسمح للمطورين بتجاوز `BlocObserver` -و/أو `EventTransformer` لـ `Zone` معينة بدلاً من الاعتماد على singleton عالمي +يقدم الإصدار v8.0.0 فئة `BlocOverrides` التي تتيح للمطورين تخصيص `BlocObserver` +و/أو `EventTransformer` ضمن `Zone` محددة، بدلاً من الاعتماد على singleton عام قابل للتغيير. **v7.x.x** @@ -309,11 +307,12 @@ void main() { } ``` -ستستخدم مثيلات `Bloc` الـ `BlocObserver` و/أو الـ `EventTransformer` للـ `Zone` -الحالية عبر `BlocOverrides.current`. إذا لم تكن هناك `BlocOverrides` للـ zone، -فستستخدم القيم الافتراضية الداخلية الموجودة (لا تغيير في السلوك/الوظيفة). +ستستخدم كائنات `Bloc` كلًا من `BlocObserver` و/أو `EventTransformer` الخاصة بالـ +`Zone` الحالية عبر `BlocOverrides.current`. في حال عدم وجود `BlocOverrides` لهذه +الـ `Zone`، سيتم استخدام القيم الافتراضية الداخلية (دون أي تغيير في السلوك أو +الوظائف). -هذا يسمح لكل `Zone` بالعمل بشكل مستقل مع `BlocOverrides` الخاصة بها. +يسمح ذلك لكل `Zone` بالعمل بشكل مستقل باستخدام `BlocOverrides` الخاصة بها. ```dart BlocOverrides.runZoned( @@ -348,56 +347,58 @@ BlocOverrides.runZoned( :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، تمت إزالة `BlocUnhandledErrorException`. بالإضافة إلى ذلك، -يتم دائماً الإبلاغ عن أي استثناءات غير ملتقطة إلى `onError` وإعادة رميها (بغض -النظر عن وضع التصحيح "debug" أو الإصدار "release"). يقوم API الـ `addError` -بالإبلاغ عن الأخطاء إلى `onError` ولكنه لا يعامل الأخطاء المبلغ عنها كاستثناءات -غير ملتقطة. +في الإصدار bloc v8.0.0، تمت إزالة `BlocUnhandledErrorException`. + +كما يتم الآن دائمًا الإبلاغ عن أي استثناء غير مُعالج إلى `onError` ثم إعادة رميه +(بغض النظر عن وضع debug أو release). + +أما `addError`، فيقوم بالإبلاغ عن الأخطاء إلى `onError`، لكنه لا يعتبر هذه +الأخطاء استثناءات غير مُعالجة. ::: -##### الأسباب (Rationale) +##### السبب + +تهدف هذه التغييرات إلى: + +- جعل الاستثناءات غير المُعالجة واضحة بشكل أكبر مع الحفاظ على استقرار bloc +- دعم استخدام `addError` دون التأثير على تدفق التنفيذ -الهدف من هذه التغييرات هو: +سابقًا، كان سلوك معالجة الأخطاء يختلف بين وضعي debug و release. -- جعل الاستثناءات الداخلية غير المعالجة واضحة للغاية مع الحفاظ على وظائف bloc. -- دعم `addError` دون تعطيل تدفق التحكم. +كما أن الأخطاء المُبلغ عنها عبر `addError` كانت تُعامل كاستثناءات غير مُعالجة في +وضع debug، مما كان يؤدي إلى تجربة غير جيدة للمطورين (خصوصًا عند كتابة اختبارات +الوحدة). -سابقاً، كان التعامل مع الأخطاء والإبلاغ عنها يختلف اعتماداً على ما إذا كان -التطبيق يعمل في وضع debug أو release. بالإضافة إلى ذلك، كانت الأخطاء المبلغ عنها -عبر `addError` تُعامل كاستثناءات غير ملتقطة في وضع debug، مما أدى إلى تجربة مطور -سيئة عند استخدام API الـ `addError` (تحديداً عند كتابة اختبارات الوحدة). +في الإصدار v8.0.0، يمكن استخدام `addError` بأمان للإبلاغ عن الأخطاء، كما يمكن +استخدام `blocTest` للتحقق من ذلك. -في v8.0.0، يمكن استخدام `addError` بأمان للإبلاغ عن الأخطاء ويمكن استخدام -`blocTest` للتحقق من الإبلاغ عن الأخطاء. لا تزال جميع الأخطاء تُبلغ إلى -`onError`؛ ومع ذلك، يتم فقط إعادة رمي الاستثناءات غير الملتقطة (بغض النظر عن وضع -debug أو release). +لا تزال جميع الأخطاء تُرسل إلى `onError`، ولكن يتم إعادة رمي الاستثناءات غير +المُعالجة فقط (بغض النظر عن وضع التشغيل). -#### ❗🧹 جعل `BlocObserver` فئة مجردة (abstract) +#### ❗🧹 جعل `BlocObserver` فئة مجردة :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، تم تحويل `BlocObserver` إلى فئة `abstract` مما يعني أنه لا -يمكن إنشاء مثيل (instance) من `BlocObserver` مباشرة. +في الإصدار bloc v8.0.0، تم تحويل `BlocObserver` إلى فئة `abstract`، مما يعني أنه +لا يمكن إنشاء instance منها مباشرة. ::: -##### الأسباب (Rationale) +##### السبب -كان المقصود من `BlocObserver` أن يكون واجهة (interface). وبما أن التطبيقات -الافتراضية للـ API هي عمليات فارغة (no-ops)، فإن `BlocObserver` الآن فئة -`abstract` للتواصل بوضوح بأن الفئة مخصصة للتوسيع وليس لإنشاء مثيلات منها مباشرة. +تم تصميم `BlocObserver` ليكون بمثابة واجهة (interface). -**v7.x.x** +وبما أن التنفيذ الافتراضي لا يحتوي على أي سلوك (no-op)، فقد تم تحويله إلى فئة +`abstract` لتوضيح أنه مخصص للتوسيع (extension) وليس للاستخدام المباشر. ```dart void main() { // كان من الممكن إنشاء مثيل من الفئة الأساسية. -``` - -final observer = BlocObserver(); } +final observer = BlocObserver(); +} -```` +``` **v8.0.0** @@ -411,71 +412,76 @@ void main() { // قم بتوسيع `BlocObserver` بدلاً من ذلك. final observer = MyBlocObserver(); // مقبول (OK) } -```` +``` -#### ❗✨ استدعاء `add` يرمي `StateError` إذا كان الـ Bloc مغلقاً +#### ❗✨ استدعاء `add` يرمي `StateError` عند إغلاق الـ Bloc :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، سيؤدي استدعاء `add` على bloc مغلق إلى حدوث `StateError`. +في الإصدار bloc v8.0.0، يؤدي استدعاء `add` على Bloc مغلق إلى رمي `StateError`. ::: -##### الأسباب (Rationale) +##### السبب -سابقاً، كان من الممكن استدعاء `add` على bloc مغلق وكان الخطأ الداخلي يتم تجاهله، -مما يجعل من الصعب تصحيح سبب عدم معالجة الحدث المضاف. لجعل هذا السيناريو أكثر -وضوحاً، في v8.0.0، سيؤدي استدعاء `add` على bloc مغلق إلى رمي `StateError` والذي -سيتم الإبلاغ عنه كاستثناء غير ملتقط وتمريره إلى `onError`. +سابقًا، كان من الممكن استدعاء `add` على Bloc مغلق، وكان الخطأ الداخلي يتم +تجاهله، مما يجعل من الصعب معرفة سبب عدم معالجة الحدث. -#### ❗✨ استدعاء `emit` يرمي `StateError` إذا كان الـ Bloc مغلقاً +لجعل هذا السيناريو أكثر وضوحًا، في v8.0.0، يؤدي استدعاء `add` على Bloc مغلق إلى +رمي `StateError`، ويتم التعامل معه كاستثناء غير مُعالج ويتم تمريره إلى +`onError`. + +#### ❗✨ استدعاء `emit` يرمي `StateError` عند إغلاق الـ Bloc :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، سيؤدي استدعاء `emit` داخل bloc مغلق إلى حدوث `StateError`. +في الإصدار bloc v8.0.0، يؤدي استدعاء `emit` داخل Bloc مغلق إلى رمي `StateError`. ::: -##### الأسباب (Rationale) +##### السبب -سابقاً، كان من الممكن استدعاء `emit` داخل bloc مغلق ولم يكن يحدث أي تغيير في -الحالة، ولكن لم يكن هناك أيضاً أي إشارة إلى الخطأ، مما يجعل التصحيح صعباً. لجعل -هذا السيناريو أكثر وضوحاً، في v8.0.0، سيؤدي استدعاء `emit` داخل bloc مغلق إلى -رمي `StateError` والذي سيتم الإبلاغ عنه كاستثناء غير ملتقط وتمريره إلى +سابقًا، كان من الممكن استدعاء `emit` داخل Bloc مغلق دون حدوث أي تغيير في الحالة، +ودون وجود أي مؤشر على الخطأ، مما يجعل عملية التصحيح صعبة. + +لجعل هذا السلوك أكثر وضوحًا، في v8.0.0، يؤدي استدعاء `emit` داخل Bloc مغلق إلى +رمي `StateError`، ويتم التعامل معه كاستثناء غير مُعالج ويتم تمريره إلى `onError`. -#### ❗🧹 إزالة واجهات برمجة التطبيقات (APIs) المهجورة +#### ❗🧹 إزالة الواجهات المهجورة :::note[ما الذي تغير؟] -في إصدار bloc v8.0.0، تمت إزالة جميع واجهات برمجة التطبيقات التي تم وضع علامة -"مهجورة" عليها سابقاً. +في الإصدار bloc v8.0.0، تمت إزالة جميع الواجهات التي تم الإعلان عنها سابقًا كـ +مهجورة (deprecated). ::: ##### ملخص -- تمت إزالة `mapEventToState` لصالح `on`. -- تمت إزالة `transformEvents` لصالح API الـ `EventTransformer`. -- تمت إزالة تعريف النوع `TransitionFunction` لصالح API الـ `EventTransformer`. -- تمت إزالة `listen` لصالح `stream.listen`. +- تمت إزالة `mapEventToState` واستبداله بـ `on` +- تمت إزالة `transformEvents` واستبداله بـ `EventTransformer` +- تمت إزالة `TransitionFunction` واستبداله بـ `EventTransformer` +- تمت إزالة `listen` واستبداله بـ `stream.listen` ### `package:bloc_test` -#### ✨ `MockBloc` و `MockCubit` لم يعودا يتطلبان `registerFallbackValue` +#### ✨ لم يعد `MockBloc` و `MockCubit` يتطلبان `registerFallbackValue` :::note[ما الذي تغير؟] -في إصدار bloc_test v9.0.0، لم يعد المطورون بحاجة إلى استدعاء -`registerFallbackValue` صراحةً عند استخدام `MockBloc` أو `MockCubit`. +في الإصدار bloc_test v9.0.0، لم يعد من الضروري استدعاء `registerFallbackValue` +عند استخدام `MockBloc` أو `MockCubit`. ::: ##### ملخص -تكون هناك حاجة لـ `registerFallbackValue` فقط عند استخدام المطابق `any()` من -`package:mocktail` لنوع مخصص. سابقاً، كانت هناك حاجة لـ `registerFallbackValue` -لكل `Event` و `State` عند استخدام `MockBloc` أو `MockCubit`. +يُستخدم `registerFallbackValue` فقط عند استخدام `any()` من `package:mocktail` مع +أنواع مخصصة. + +سابقًا، كان يجب استدعاؤه لكل `Event` و `State` عند استخدام `MockBloc` أو +`MockCubit`. **v8.x.x** @@ -506,26 +512,27 @@ void main() { ### `package:hydrated_bloc` -#### ❗✨ تقديم API الـ `HydratedBlocOverrides` الجديد +#### ❗✨ تقديم `HydratedBlocOverrides` :::note[ما الذي تغير؟] -في إصدار hydrated_bloc v8.0.0، تمت إزالة `HydratedBloc.storage` لصالح API الـ +في الإصدار hydrated_bloc v8.0.0، تمت إزالة `HydratedBloc.storage` واستبداله بـ `HydratedBlocOverrides`. ::: -##### الأسباب (Rationale) +##### السبب -سابقاً، كان يتم استخدام singleton عالمي لتجاوز تطبيق الـ `Storage`. +سابقًا، كان يتم الاعتماد على singleto عام لتخصيص تنفيذ `Storage`. -ونتيجة لذلك، لم يكن من الممكن امتلاك تطبيقات `Storage` متعددة محصورة في أجزاء -مختلفة من التطبيق. كما كان من الصعب أيضاً إجراء الاختبارات بسبب الحالة العالمية -المشتركة عبر الاختبارات. +ونتيجة لذلك، لم يكن من الممكن استخدام أكثر من تنفيذ لـ `Storage` ضمن أجزاء +مختلفة من التطبيق. -يقدم `HydratedBloc` v8.0.0 فئة `HydratedBlocOverrides` التي تسمح للمطورين بتجاوز -الـ `Storage` لـ `Zone` معينة بدلاً من الاعتماد على singleton عالمي قابل -للتغيير. +كما أن هذا النهج جعل الاختبارات أكثر صعوبة بسبب وجود حالة عامة مشتركة بين +الاختبارات. + +يقدم الإصدار v8.0.0 فئة `HydratedBlocOverrides` التي تتيح للمطورين تخصيص +`Storage` ضمن `Zone` محددة، بدلاً من الاعتماد على singleton عام قابل للتغيير. **v7.x.x** @@ -556,47 +563,52 @@ void main() { } ``` -ستستخدم مثيلات `HydratedBloc` الـ `Storage` للـ `Zone` الحالية عبر +ستستخدم كائنات `HydratedBloc` الـ `Storage` الخاصة بالـ `Zone` الحالية عبر `HydratedBlocOverrides.current`. -هذا يسمح لكل `Zone` بالعمل بشكل مستقل مع `BlocOverrides` الخاصة بها. +يسمح ذلك لكل `Zone` بالعمل بشكل مستقل باستخدام الإعدادات الخاصة بها. ## v7.2.0 ### `package:bloc` -#### ✨ تقديم API الـ `on` الجديد +#### ✨ تقديم `on` :::note[ما الذي تغير؟] -في إصدار bloc v7.2.0، تم وضع علامة "مهجور" على `mapEventToState` لصالح -`on`. سيتم إزالة `mapEventToState` في إصدار bloc v8.0.0. +في الإصدار bloc v7.2.0، تم إيقاف استخدام `mapEventToState` (deprecated) +واستبداله بـ `on`. وسيتم إزالة `mapEventToState` في الإصدار v8.0.0. ::: -##### الأسباب (Rationale) +##### السبب -تم تقديم API الـ `on` كجزء من -[مقترح استبدال mapEventToState بـ on\ في Bloc](https://github.com/felangel/bloc/issues/2526). -بسبب [مشكلة في Dart](https://github.com/dart-lang/sdk/issues/44616)، ليس من -الواضح دائماً ما ستكون عليه قيمة الـ `state` عند التعامل مع مولدات غير متزامنة -متداخلة (`async*`). على الرغم من وجود طرق للالتفاف على المشكلة، إلا أن أحد -المبادئ الأساسية لمكتبة bloc هو أن تكون قابلة للتنبؤ. تم إنشاء API الـ -`on` لجعل المكتبة آمنة قدر الإمكان للاستخدام وللقضاء على أي شك فيما يتعلق -بتغييرات الحالة. +تم تقديم `on` كجزء من: +[مقترح: استبدال mapEventToState بـ `on` في Bloc](https://github.com/felangel/bloc/issues/2526). + +بسبب [مشكلة في Dart](https://github.com/dart-lang/sdk/issues/44616)، قد لا يكون +من الواضح دائمًا ما ستكون عليه قيمة `state` عند التعامل مع مولدات غير متزامنة +متداخلة (`async*`). + +على الرغم من وجود حلول بديلة، إلا أن أحد المبادئ الأساسية لمكتبة bloc هو +القابلية للتنبؤ. + +لذلك، تم تقديم `on` لجعل استخدام المكتبة أكثر أمانًا وتقليل أي غموض متعلق +بتغيّر الحالة. :::tip -لمزيد من المعلومات، -[اقرأ المقترح الكامل](https://github.com/felangel/bloc/issues/2526). +لمزيد من التفاصيل: +[اقرأ المقترح الكامل](https://github.com/felangel/bloc/issues/2526) ::: ##### ملخص -يسمح لك `on` بتسجيل معالج أحداث (event handler) لجميع الأحداث من النوع `E`. -بشكل افتراضي، سيتم معالجة الأحداث بشكل متزامن (concurrently) عند استخدام `on` -على عكس `mapEventToState` الذي يعالج الأحداث بشكل تسلسلي (`sequentially`). +يتيح لك `on` تسجيل معالج أحداث لجميع الأحداث من النوع `E`. + +بشكل افتراضي، تتم معالجة الأحداث بشكل متوازي (concurrently) عند استخدام `on`، +على عكس `mapEventToState` الذي يعالج الأحداث بشكل تسلسلي (sequentially). **v7.1.0** @@ -631,13 +643,13 @@ class CounterBloc extends Bloc { :::note -يعمل كل `EventHandler` مسجل بشكل مستقل، لذا من المهم تسجيل معالجات الأحداث بناءً -على نوع المحول (transformer) الذي ترغب في تطبيقه. +يعمل كل `EventHandler` بشكل مستقل، لذلك من المهم تسجيل معالجات الأحداث بناءً على +نوع الـ transformer الذي ترغب في استخدامه. ::: -إذا كنت ترغب في الاحتفاظ بنفس السلوك تماماً كما في v7.1.0، يمكنك تسجيل معالج -أحداث واحد لجميع الأحداث وتطبيق محول تسلسلي (`sequential` transformer): +إذا كنت ترغب في الحفاظ على نفس السلوك تمامًا كما في v7.1.0، يمكنك تسجيل معالج +أحداث واحد لجميع الأحداث وتطبيق transformer تسلسلي (`sequential`). ```dart import 'package:bloc/bloc.dart'; @@ -654,7 +666,7 @@ class MyBloc extends Bloc { } ``` -يمكنك أيضاً تجاوز الـ `EventTransformer` الافتراضي لجميع الـ blocs في تطبيقك: +يمكنك أيضًا تجاوز `EventTransformer` الافتراضي لجميع الـ blocs في تطبيقك: ```dart import 'package:bloc/bloc.dart'; @@ -666,26 +678,29 @@ void main() { } ``` -#### ✨ تقديم API الـ `EventTransformer` الجديد +#### ✨ تقديم `EventTransformer` :::note[ما الذي تغير؟] -في إصدار bloc v7.2.0، تم وضع علامة "مهجور" على `transformEvents` لصالح API الـ -`EventTransformer`. سيتم إزالة `transformEvents` في إصدار bloc v8.0.0. +في الإصدار bloc v7.2.0، تم إيقاف استخدام `transformEvents` (deprecated) +واستبداله بـ `EventTransformer`. وسيتم إزالة `transformEvents` في الإصدار +v8.0.0. ::: -##### الأسباب (Rationale) +##### السبب -فتح API الـ `on` الباب أمام القدرة على توفير محول أحداث مخصص لكل معالج -أحداث. تم تقديم تعريف نوع (typedef) جديد باسم `EventTransformer` والذي يمكّن -المطورين من تحويل تدفق الأحداث الواردة لكل معالج أحداث بدلاً من الاضطرار إلى -تحديد محول أحداث واحد لجميع الأحداث. +أتاح `on` إمكانية تخصيص event transformer لكل معالج أحداث (event +handler). + +تم تقديم typedef جديد باسم `EventTransformer`، والذي يتيح للمطورين تحويل تدفق +الأحداث الواردة لكل معالج بشكل مستقل، بدلاً من استخدام transformer واحد لكافة +الأحداث. ##### ملخص -الـ `EventTransformer` مسؤول عن أخذ تدفق الأحداث الواردة جنباً إلى جنب مع -`EventMapper` (معالج الأحداث الخاص بك) وإرجاع تدفق جديد من الأحداث. +يقوم `EventTransformer` باستقبال تدفق الأحداث الواردة مع `EventMapper` (معالج +الأحداث)، وإرجاع تدفق جديد من الأحداث. ```dart typedef EventTransformer = Stream Function(Stream events, EventMapper mapper) @@ -721,31 +736,32 @@ Stream> transformEvents(events, transitionFn) { **v7.2.0** ```dart -/// تعريف `EventTransformer` مخصص +/// تعريف EventTransformer مخصص EventTransformer debounce(Duration duration) { return (events, mapper) => events.debounceTime(duration).flatMap(mapper); } MyBloc() : super(MyState()) { - /// تطبيق الـ `EventTransformer` المخصص على الـ `EventHandler` + /// تطبيق EventTransformer المخصص على معالج الحدث on(_onEvent, transformer: debounce(const Duration(milliseconds: 300))) } ``` -#### ⚠️ وضع علامة "مهجور" على API الـ `transformTransitions` +#### ⚠️ إيقاف استخدام `transformTransitions` :::note[ما الذي تغير؟] -في إصدار bloc v7.2.0، تم وضع علامة "مهجور" على `transformTransitions` لصالح -تجاوز API الـ `stream`. سيتم إزالة `transformTransitions` في إصدار bloc v8.0.0. +في الإصدار bloc v7.2.0، تم إيقاف استخدام `transformTransitions` (deprecated) +واستبداله بتخصيص `stream`. + +وسيتم إزالة `transformTransitions` في الإصدار v8.0.0. ::: -##### الأسباب (Rationale) +##### السبب -تجعل أداة الحصول (getter) الـ `stream` في `Bloc` من السهل تجاوز تدفق الحالات -الصادر، وبالتالي لم يعد من المفيد الحفاظ على API منفصل لـ -`transformTransitions`. +يتيح getter الخاص بـ `stream` في `Bloc` تخصيص تدفق الحالات (states) الخارجة +بسهولة، لذلك لم تعد هناك حاجة للاحتفاظ بواجهة منفصلة مثل `transformTransitions`. ##### ملخص @@ -771,38 +787,46 @@ Stream get stream => super.stream.debounceTime(const Duration(millisecond ### `package:bloc` -#### ❗ الـ Bloc والـ Cubit يوسعان `BlocBase` +#### ❗ أصبح كل من `Bloc` و `Cubit` يعتمدان على `BlocBase` -##### الأسباب (Rationale) +##### السبب + +كمطور، كانت العلاقة بين blocs و cubits غير واضحة إلى حدٍ ما. + +عند تقديم Cubit لأول مرة، كان يُستخدم كفئة أساسية (base class) لـ blocs، وهو ما +كان منطقيًا لأنه يوفر جزءًا من الوظائف، بينما تقوم blocs بتوسيعه وإضافة المزيد +من الـ APIs. -كمطور، كانت العلاقة بين الـ blocs والـ cubits غريبة بعض الشيء. عندما تم تقديم -cubit لأول مرة، بدأ كفئة أساسية للـ blocs، وهو ما كان منطقياً لأنه كان يحتوي على -مجموعة فرعية من الوظائف وكان الـ blocs يوسعون Cubit ببساطة ويعرفون واجهات برمجة -تطبيقات إضافية. جاء ذلك مع بعض العيوب: +لكن هذا التصميم أدى إلى بعض المشاكل: -- كان لابد من إعادة تسمية جميع واجهات برمجة التطبيقات لتقبل cubit من أجل الدقة، - أو كان لابد من إبقائها كـ bloc من أجل الاتساق على الرغم من أنها غير دقيقة - هرمياً ([#1708](https://github.com/felangel/bloc/issues/1708)، - [#1560](https://github.com/felangel/bloc/issues/1560)). +- كان من الضروري إما إعادة تسمية APIs لتدعم cubit بشكل دقيق، أو الإبقاء على + تسميات bloc للحفاظ على الاتساق، رغم أن ذلك غير دقيق من الناحية الهيكلية + ([#1708](https://github.com/felangel/bloc/issues/1708)، [#1560](https://github.com/felangel/bloc/issues/1560)). -- كان على Cubit توسيع Stream وتطبيق EventSink من أجل الحصول على قاعدة مشتركة - يمكن بناء عناصر واجهة المستخدم مثل BlocBuilder و BlocListener وغيرها عليها +- كان على Cubit أن يرث من `Stream` ويطبق `EventSink` لتوفير قاعدة مشتركة يمكن أن + تعتمد عليها مكونات مثل `BlocBuilder` و `BlocListener` ([#1429](https://github.com/felangel/bloc/issues/1429)). -لاحقاً، جربنا عكس العلاقة وجعل bloc هو القاعدة +--- + +لاحقًا، تم تجربة عكس العلاقة وجعل bloc هو الفئة الأساسية. + +هذا الحل عالج جزئيًا المشكلة الأولى، لكنه تسبب في مشاكل أخرى: -والذي حل جزئياً النقطة الأولى أعلاه ولكنه قدم مشاكل أخرى: +- أصبح API الخاص بـ cubit معقدًا بسبب وراثة APIs من bloc مثل `mapEventToState` و + `add` وغيرها + ([#2228](https://github.com/felangel/bloc/issues/2228)) -- أصبح API الـ cubit متضخماً بسبب واجهات برمجة تطبيقات bloc الأساسية مثل - mapEventToState و add وغيرها - ([#2228](https://github.com/felangel/bloc/issues/2228)). - - يمكن للمطورين تقنياً استدعاء هذه الواجهات وتخريب الأشياء. -- لا نزال نواجه نفس المشكلة المتمثلة في كشف cubit لكامل API الـ stream كما كان - من قبل ([#1429](https://github.com/felangel/bloc/issues/1429)). + - ويمكن للمطورين استخدام هذه APIs بشكل غير صحيح مما يؤدي إلى مشاكل -لمعالجة هذه المشكلات، قدمنا فئة أساسية لكل من `Bloc` و `Cubit` تسمى `BlocBase` -بحيث لا تزال المكونات العلوية قادرة على التعامل مع كل من مثيلات bloc و cubit -ولكن دون كشف كامل واجهات `Stream` و `EventSink` مباشرة. +- استمرت مشكلة كشف cubit لكامل واجهة `Stream` كما كانت سابقًا + ([#1429](https://github.com/felangel/bloc/issues/1429)) + +لحل هذه التحديات، تم تقديم فئة أساسية جديدة مشتركة بين `Bloc` و `Cubit` باسم +`BlocBase`. + +يسمح ذلك للمكونات الأعلى في التطبيق بالتعامل مع bloc و cubit بطريقة موحدة، دون +الحاجة إلى كشف كامل واجهات `Stream` و `EventSink`. ##### ملخص @@ -880,12 +904,12 @@ cubit.stream.listen((state) {...}); ### `package:bloc_test` -#### ❗ `seed` ترجع دالة لدعم القيم الديناميكية +#### ❗ `seed` تُعيد دالة لدعم القيم الديناميكية -##### الأسباب (Rationale) +##### السبب -من أجل دعم وجود قيمة "بذرة" (seed) قابلة للتغيير ويمكن تحديثها ديناميكياً في -`setUp` ، فإن `seed` ترجع دالة. +لدعم استخدام قيمة seed قابلة للتغيير ويمكن تحديثها ديناميكيًا داخل `setUp`، +أصبحت `seed` تُعيد دالة بدلاً من قيمة ثابتة. ##### ملخص @@ -909,12 +933,14 @@ blocTest( ); ``` -#### ❗ `expect` ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher +#### ❗ `expect` تُعيد دالة لدعم القيم الديناميكية مع دعم `Matchers` -##### الأسباب (Rationale) +##### السبب -من أجل دعم وجود توقع (expectation) قابل للتغيير ويمكن تحديثه ديناميكياً في -`setUp` ، فإن `expect` ترجع دالة. كما يدعم `expect` أيضاً الـ `Matchers`. +لدعم استخدام توقع (expectation) قابل للتغيير ويمكن تحديثه ديناميكيًا داخل +`setUp`، أصبحت `expect` تُعيد دالة بدلاً من قيمة ثابتة. + +كما تدعم `expect` استخدام `Matchers`. ##### ملخص @@ -945,12 +971,14 @@ blocTest( ); ``` -#### ❗ `errors` ترجع دالة لدعم القيم الديناميكية وتتضمن دعم الـ matcher +#### ❗ `errors` تُعيد دالة لدعم القيم الديناميكية مع دعم `Matchers` -##### الأسباب (Rationale) +##### السبب + +لدعم استخدام قائمة أخطاء (errors) قابلة للتغيير ويمكن تحديثها ديناميكيًا داخل +`setUp`، أصبحت `errors` تُعيد دالة بدلاً من قيمة ثابتة. -من أجل دعم وجود أخطاء قابلة للتغيير ويمكن تحديثها ديناميكياً في `setUp` ، فإن -`errors` ترجع دالة. كما يدعم `errors` أيضاً الـ `Matchers`. +كما تدعم `errors` استخدام `Matchers`. ##### ملخص @@ -981,13 +1009,15 @@ blocTest( ); ``` -#### ❗ MockBloc و MockCubit +#### ❗ `MockBloc` و `MockCubit` -##### الأسباب (Rationale) +##### السبب + +لدعم محاكاة (stubbing) واجهات برمجة التطبيقات الأساسية بشكل أفضل، تم توفير +`MockBloc` و `MockCubit` ضمن حزمة `bloc_test`. -لدعم محاكاة (stubbing) مختلف واجهات برمجة التطبيقات الأساسية، يتم تصدير -`MockBloc` و `MockCubit` كجزء من حزمة `bloc_test`. سابقاً، كان يجب استخدام -`MockBloc` لكل من مثيلات `Bloc` و `Cubit` وهو ما لم يكن بديهياً. +سابقًا، كان يتم استخدام `MockBloc` لكل من `Bloc` و `Cubit`، وهو ما لم يكن واضحًا +أو بديهيًا للمطورين. ##### ملخص @@ -1005,16 +1035,18 @@ class MockMyBloc extends MockBloc implements MyBloc {} class MockMyCubit extends MockCubit implements MyCubit {} ``` -#### ❗ التكامل مع Mocktail +#### ❗ التكامل مع `Mocktail` -##### الأسباب (Rationale) +##### السبب -نظراً للقيود المختلفة لحزمة [package:mockito](https://pub.dev/packages/mockito) -الآمنة من القيم الخالية (null-safe) والموضحة +بسبب القيود المختلفة في الحزمة الداعمة لـ null-safety +[package:mockito](https://pub.dev/packages/mockito) والمُوضحة [هنا](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md#problems-with-typical-mocking-and-stubbing)، -يتم استخدام [package:mocktail](https://pub.dev/packages/mocktail) بواسطة -`MockBloc` و `MockCubit`. يتيح ذلك للمطورين الاستمرار في استخدام واجهة محاكاة -مألوفة دون الحاجة إلى كتابة stubs يدوياً أو الاعتماد على توليد الكود. +تم اعتماد [package:mocktail](https://pub.dev/packages/mocktail) في `MockBloc` و +`MockCubit`. + +يتيح ذلك للمطورين استخدام واجهة محاكاة مألوفة دون الحاجة إلى كتابة stubs يدويًا +أو الاعتماد على توليد الكود. ##### ملخص @@ -1043,18 +1075,21 @@ verify(() => bloc.add(any())).called(1); > يرجى الرجوع إلى [#347](https://github.com/dart-lang/mockito/issues/347) > بالإضافة إلى > [توثيق mocktail](https://github.com/felangel/mocktail/tree/main/packages/mocktail) -> لمزيد من المعلومات. +> لمزيد من التفاصيل. ### `package:flutter_bloc` -#### ❗ إعادة تسمية معامل `cubit` إلى `bloc` +#### ❗ إعادة تسمية المعامل `cubit` إلى `bloc` -##### الأسباب (Rationale) +##### السبب -نتيجة لإعادة الهيكلة في `package:bloc` لتقديم `BlocBase` الذي يوسعه كل من `Bloc` -و `Cubit` ، تمت إعادة تسمية معاملات `BlocBuilder` و `BlocConsumer` و -`BlocListener` من `cubit` إلى `bloc` لأن هذه العناصر تعمل على نوع `BlocBase`. -كما أن هذا يتماشى بشكل أكبر مع اسم المكتبة ونأمل أن يحسن من قابلية القراءة. +نتيجة لإعادة الهيكلة في `package:bloc` وتقديم `BlocBase` الذي يتم توريثه من قبل +كلٍ من `Bloc` و `Cubit`، تم تغيير اسم المعامل في `BlocBuilder` و `BlocConsumer` +و `BlocListener` من `cubit` إلى `bloc`، لأن هذه الواجهات تعمل على النوع +`BlocBase`. + +كما أن هذا التغيير يتماشى بشكل أفضل مع اسم المكتبة، ويساهم في تحسين وضوح وقابلية +قراءة الكود. ##### ملخص @@ -1098,14 +1133,16 @@ BlocConsumer( ### `package:hydrated_bloc` -#### ❗ `storageDirectory` مطلوب عند استدعاء `HydratedStorage.build` +#### ❗ أصبح `storageDirectory` مطلوبًا عند استدعاء `HydratedStorage.build` -##### الأسباب (Rationale) +##### السبب -من أجل جعل `package:hydrated_bloc` حزمة Dart صرفة، تمت إزالة الاعتماد على -[package:path_provider](https://pub.dev/packages/path_provider) وأصبح معامل -`storageDirectory` عند استدعاء `HydratedStorage.build` مطلوباً ولم يعد يعود -افتراضياً إلى `getTemporaryDirectory`. +لجعل `package:hydrated_bloc` حزمة Dart صِرفة، تم إزالة الاعتماد على +[package:path_provider](https://pub.dev/packages/path_provider). + +وبالتالي، أصبح تمرير المعامل `storageDirectory` عند استدعاء +`HydratedStorage.build` إلزاميًا، ولم يعد يتم تعيينه تلقائيًا إلى +`getTemporaryDirectory`. ##### ملخص @@ -1131,23 +1168,23 @@ HydratedBloc.storage = await HydratedStorage.build( ### `package:flutter_bloc` -#### ❗ وضع علامة "مهجور" على `context.bloc` و `context.repository` لصالح `context.read` و `context.watch` +#### ❗ تم وضع علامة "مهجور" على `context.bloc` و `context.repository` لصالح `context.read` و `context.watch` -##### الأسباب (Rationale) +##### السبب + +تم تقديم `context.read` و `context.watch` و `context.select` لتتوافق مع واجهة +برمجة التطبيقات الخاصة بـ [provider](https://pub.dev/packages/provider)، والتي +يألفها العديد من المطورين، وكذلك لمعالجة المشكلات التي تم طرحها من قبل المجتمع. -تمت إضافة `context.read` و `context.watch` و `context.select` لتتماشى مع واجهة -برمجة تطبيقات [provider](https://pub.dev/packages/provider) الحالية التي يألفها -العديد من المطورين وللمعالجة المشكلات التي أثارها المجتمع. لتحسين أمان الكود -والحفاظ على الاتساق، تم وضع علامة "مهجور" على `context.bloc` لأنه يمكن استبداله -بـ `context.read` أو `context.watch` اعتماداً على ما إذا كان يتم استخدامه مباشرة -داخل `build`. +ولتحسين أمان الكود والحفاظ على الاتساق، تم وضع علامة "مهجور" على `context.bloc`، +حيث يمكن استبداله بـ `context.read` أو `context.watch` اعتمادًا على ما إذا كان +يتم استخدامه مباشرة داخل `build`. -**context.watch** +##### `context.watch` -يعالج `context.watch` طلب الحصول على -[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538) لأنه يمكننا -مراقبة عدة blocs داخل `Builder` واحد من أجل عرض واجهة المستخدم بناءً على حالات -متعددة: +يوفر `context.watch` بديلاً عمليًا لطلب +[MultiBlocBuilder](https://github.com/felangel/bloc/issues/538)، حيث يمكن مراقبة +عدة blocs داخل `Builder` واحد من أجل بناء واجهة المستخدم بناءً على حالات متعددة. ```dart Builder( @@ -1161,11 +1198,11 @@ Builder( ); ``` -**context.select** +##### `context.select` -يسمح `context.select` للمطورين بعرض/تحديث واجهة المستخدم بناءً على جزء من حالة -الـ bloc ويعالج طلب الحصول على -[buildWhen أبسط](https://github.com/felangel/bloc/issues/1521). +يتيح `context.select` للمطورين إعادة بناء (rebuild) واجهة المستخدم بناءً على جزء +محدد من حالة الـ bloc، كما يوفر بديلاً أبسط لاستخدام +[buildWhen](https://github.com/felangel/bloc/issues/1521). ```dart final name = context.select((UserBloc bloc) => bloc.state.user.name); @@ -1174,13 +1211,14 @@ final name = context.select((UserBloc bloc) => bloc.state.user.name); تسمح لنا القطعة البرمجية أعلاه بالوصول إلى الـ widget وإعادة بنائه فقط عندما يتغير اسم المستخدم الحالي. -**context.read** +##### `context.read` -على الرغم من أن `context.read` يبدو مطابقاً لـ `context.bloc` ، إلا أن هناك بعض -الاختلافات الدقيقة ولكن الهامة. كلاهما يسمح لك بالوصول إلى bloc باستخدام -`BuildContext` ولا يؤديان إلى إعادة البناء؛ ومع ذلك، لا يمكن استدعاء -`context.read` مباشرة داخل طريقة `build`. هناك سببان رئيسيان لاستخدام -`context.bloc` داخل `build`: +على الرغم من أن `context.read` يبدو مشابهًا لـ `context.bloc`، إلا أن هناك +اختلافات دقيقة لكنها مهمة. كلاهما يتيح لك الوصول إلى الـ bloc باستخدام +`BuildContext` دون التسبب في إعادة بناء (rebuild)؛ ومع ذلك، لا يمكن استخدام +`context.read` مباشرة داخل دالة `build`. + +كان هناك سببان رئيسيان لاستخدام `context.bloc` داخل `build`: 1. **للوصول إلى حالة الـ bloc** @@ -1192,8 +1230,9 @@ Widget build(BuildContext context) { } ``` -الاستخدام أعلاه عرضة للخطأ لأن عنصر الـ `Text` لن يتم إعادة بنائه إذا تغيرت حالة -الـ bloc. في هذا السيناريو، يجب استخدام إما `BlocBuilder` أو `context.watch`. +يُعد الاستخدام أعلاه عرضة للأخطاء، لأن عنصر `Text` لن تتم إعادة بنائه عند تغيّر +حالة الـ bloc. في هذه الحالة، يُنصح باستخدام `BlocBuilder` أو `context.watch`. +`` ```dart @override @@ -1216,15 +1255,15 @@ Widget build(BuildContext context) { :::note -سيؤدي استخدام `context.watch` في جذر طريقة `build` إلى إعادة بناء الـ widget -بالكامل عند تغير حالة الـ bloc. إذا لم تكن هناك حاجة لإعادة بناء الـ widget -بالكامل، فاستخدم إما `BlocBuilder` لتغليف الأجزاء التي يجب إعادة بنائها، أو -استخدم `Builder` مع `context.watch` لتحديد نطاق إعادة البناء، أو قم بتفكيك الـ -widget إلى widgets أصغر. +سيؤدي استخدام `context.watch` في جذر دالة `build` إلى إعادة بناء الـ widget +بالكامل عند تغيّر حالة الـ bloc. إذا لم تكن هناك حاجة لإعادة بناء الـ widget +بالكامل، يمكنك استخدام `BlocBuilder` لتغليف الأجزاء التي تحتاج إلى إعادة بناء، +أو استخدام `Builder` مع `context.watch` لتحديد نطاق إعادة البناء، أو تقسيم الـ +widget إلى مكونات أصغر. ::: -2. **للوصول إلى الـ bloc حتى يمكن إضافة حدث** +2. **للوصول إلى الـ bloc من أجل إضافة حدث** ```dart @override @@ -1279,8 +1318,8 @@ Widget build(BuildContext context) { } ``` -?> إذا كنت تصل إلى bloc لإضافة حدث، فقم بالوصول إليه باستخدام `context.read` في -الاستدعاء (callback) حيث تبرز الحاجة إليه. +?> إذا كنت بحاجة للوصول إلى bloc لإضافة حدث، فاستخدم `context.read` داخل الـ +callback في المكان الذي تحتاجه فيه. **v6.0.x** @@ -1303,19 +1342,19 @@ Widget build(BuildContext context) { ``` ?> استخدم `context.watch` عند الوصول إلى حالة الـ bloc لضمان إعادة بناء الـ -widget عند تغير الحالة. +widget عند تغيّر الحالة. ## v6.0.0 ### `package:bloc` -#### ❗ `BlocObserver.onError` يأخذ `Cubit` +#### ❗ أصبح `BlocObserver.onError` يستقبل `Cubit` -##### الأسباب (Rationale) +##### السبب -بسبب دمج `Cubit` ، أصبحت `onError` الآن مشتركة بين كل من مثيلات `Bloc` و -`Cubit`. وبما أن `Cubit` هو القاعدة، فإن `BlocObserver` سيقبل نوع `Cubit` بدلاً -من نوع `Bloc` في تجاوز `onError`. +بسبب دمج `Cubit`، أصبحت `onError` مشتركة بين كلٍ من `Bloc` و `Cubit`. وبما أن +`Cubit` هو الفئة الأساسية، فإن `BlocObserver` يستقبل نوع `Cubit` بدلاً من `Bloc` +عند تجاوز `onError`. **v5.x.x** @@ -1339,14 +1378,16 @@ class MyBlocObserver extends BlocObserver { } ``` -#### ❗ الـ Bloc لا يرسل الحالة الأخيرة عند الاشتراك +#### ❗ لا يقوم `Bloc` بإصدار الحالة الأخيرة عند الاشتراك -##### الأسباب (Rationale) +##### السبب -تم إجراء هذا التغيير لمواءمة `Bloc` و `Cubit` مع سلوك الـ `Stream` المدمج في -`Dart`. بالإضافة إلى ذلك، أدى الالتزام بالسلوك القديم في سياق `Cubit` إلى العديد -من الآثار الجانبية غير المقصودة وعقد بشكل عام التطبيقات الداخلية للحزم الأخرى -مثل `flutter_bloc` و `bloc_test` دون داعٍ (مما تطلب استخدام `skip(1)` ، إلخ...). +تم إجراء هذا التغيير لمواءمة سلوك `Bloc` و `Cubit` مع سلوك `Stream` المدمج في +Dart. + +بالإضافة إلى ذلك، أدى الالتزام بالسلوك السابق في `Cubit` إلى ظهور العديد من +الآثار الجانبية غير المقصودة، كما زاد من تعقيد التنفيذ الداخلي لحزم أخرى مثل +`flutter_bloc` و `bloc_test` دون داعٍ (مثل الحاجة إلى استخدام `skip(1)` وغيرها). **v5.x.x** @@ -1355,13 +1396,13 @@ final bloc = MyBloc(); bloc.listen(print); ``` -سابقاً، كانت القطعة البرمجية أعلاه تطبع الحالة الأولية للـ bloc متبوعة بتغييرات -الحالة اللاحقة. +سابقًا، كان الكود أعلاه يقوم بإخراج (output) الحالة الأولية للـ bloc، تليها +تغييرات الحالة اللاحقة. **v6.x.x** -في v6.0.0، لا تطبع القطعة البرمجية أعلاه الحالة الأولية وتطبع فقط تغييرات الحالة -اللاحقة. يمكن تحقيق السلوك السابق بما يلي: +في الإصدار v6.0.0، لم يعد الكود أعلاه يخرج الحالة الأولية، وإنما يقتصر على إخراج +تغييرات الحالة اللاحقة فقط. يمكن استعادة السلوك السابق باستخدام ما يلي: ```dart final bloc = MyBloc(); @@ -1369,17 +1410,18 @@ print(bloc.state); bloc.listen(print); ``` -?> **ملاحظة**: سيؤثر هذا التغيير فقط على الكود الذي يعتمد على اشتراكات bloc -المباشرة. عند استخدام `BlocBuilder` أو `BlocListener` أو `BlocConsumer` ، لن -يكون هناك تغيير ملحوظ في السلوك. +?> **ملاحظة**: يؤثر هذا التغيير فقط على الكود الذي يعتمد على الاشتراك المباشر في +الـ bloc. عند استخدام `BlocBuilder` أو `BlocListener` أو `BlocConsumer`، لن يكون +هناك أي تغيير ملحوظ في السلوك. ### `package:bloc_test` -#### ❗ `MockBloc` يتطلب نوع الحالة (State) فقط +#### ❗ `MockBloc` يتطلب نوع الحالة (`State`) فقط -##### الأسباب (Rationale) +##### السبب -هذا ليس ضرورياً ويزيل الكود الزائد مع جعل `MockBloc` متوافقاً مع `Cubit`. +لم يعد من الضروري تحديد أنواع إضافية، مما يقلل من الكود الزائد، كما يجعل +`MockBloc` متوافقًا مع `Cubit`. **v5.x.x** @@ -1393,11 +1435,12 @@ class MockCounterBloc extends MockBloc implements CounterBloc class MockCounterBloc extends MockBloc implements CounterBloc {} ``` -#### ❗ `whenListen` يتطلب نوع الحالة (State) فقط +#### ❗ `whenListen` يتطلب نوع الحالة (`State`) فقط -##### الأسباب (Rationale) +##### السبب -هذا ليس ضرورياً ويزيل الكود الزائد مع جعل `whenListen` متوافقاً مع `Cubit`. +لم يعد من الضروري تحديد أنواع إضافية، مما يقلل من الكود الزائد، كما يجعل +`whenListen` متوافقًا مع `Cubit`. **v5.x.x** @@ -1411,11 +1454,12 @@ whenListen(bloc, Stream.fromIterable([0, 1, 2, 3])); whenListen(bloc, Stream.fromIterable([0, 1, 2, 3])); ``` -#### ❗ `blocTest` لا يتطلب نوع الحدث (Event) +#### ❗ `blocTest` لا يتطلب نوع الحدث (`Event`) -##### الأسباب (Rationale) +##### السبب -هذا ليس ضرورياً ويزيل الكود الزائد مع جعل `blocTest` متوافقاً مع `Cubit`. +لم يعد من الضروري تحديد نوع الحدث، مما يقلل من الكود الزائد، كما يجعل `blocTest` +متوافقًا مع `Cubit`. **v5.x.x** @@ -1439,12 +1483,12 @@ blocTest( ); ``` -#### ❗ القيمة الافتراضية لـ `skip` في `blocTest` هي 0 +#### ❗ القيمة الافتراضية لـ `skip` في `blocTest` أصبحت 0 -##### الأسباب (Rationale) +##### السبب -بما أن مثيلات `bloc` و `cubit` لن ترسل الحالة الأخيرة للاشتراكات الجديدة، لم يعد -من الضروري جعل القيمة الافتراضية لـ `skip` هي `1`. +بما أن مثيلات `bloc` و `cubit` لم تعد تُصدر الحالة الأخيرة عند الاشتراك الجديد، +لم يعد من الضروري تعيين القيمة الافتراضية لـ `skip` إلى `1`. **v5.x.x** @@ -1476,15 +1520,17 @@ test('initial state is correct', () { }); ``` -#### ❗ جعل `build` في `blocTest` متزامناً (synchronous) +#### ❗ جعل `build` في `blocTest` متزامنًا (synchronous) -##### الأسباب (Rationale) +##### السبب + +سابقًا، كان يتم جعل `build` غير متزامن (`async`) لإتاحة تنفيذ بعض التحضيرات من +أجل وضع الـ bloc تحت الاختبار في حالة محددة. لم يعد ذلك ضروريًا، كما أنه يساهم +في حل عدة مشكلات ناتجة عن التأخير بين مرحلة البناء (build) وبدء الاشتراك +داخليًا. -سابقاً، تم جعل `build` غير متزامن (`async`) بحيث يمكن إجراء تحضيرات متنوعة لوضع -الـ bloc تحت الاختبار في حالة معينة. لم يعد هذا ضرورياً كما أنه يحل العديد من -المشكلات الناتجة عن التأخير المضاف بين البناء والاشتراك داخلياً. بدلاً من إجراء -تحضير غير متزامن لوضع bloc في حالة مطلوبة، يمكننا الآن تعيين حالة الـ bloc عن -طريق ربط `emit` بالحالة المطلوبة. +بدلًا من الاعتماد على التحضير غير المتزامن للوصول إلى حالة معينة، يمكن الآن +تعيين حالة الـ bloc مباشرة باستخدام `emit` مع الحالة المطلوبة. **v5.x.x** @@ -1515,18 +1561,18 @@ blocTest( :::note -`emit` مرئي فقط للاختبار ولا ينبغي استخدامه أبداً خارج الاختبارات. +تُستخدم `emit` لأغراض الاختبار فقط، ولا ينبغي استخدامها خارج نطاق الاختبارات. ::: ### `package:flutter_bloc` -#### ❗ إعادة تسمية معامل `bloc` في `BlocBuilder` إلى `cubit` +#### ❗ إعادة تسمية المعامل `bloc` في `BlocBuilder` إلى `cubit` -##### الأسباب (Rationale) +##### السبب -من أجل جعل `BlocBuilder` يعمل مع مثيلات `bloc` و `cubit` ، تمت إعادة تسمية معامل -`bloc` إلى `cubit` (بما أن `Cubit` هو الفئة الأساسية). +لجعل `BlocBuilder` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل +من `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية). **v5.x.x** @@ -1546,12 +1592,12 @@ BlocBuilder( ) ``` -#### ❗ إعادة تسمية معامل `bloc` في `BlocListener` إلى `cubit` +#### ❗ إعادة تسمية المعامل `bloc` في `BlocListener` إلى `cubit` -##### الأسباب (Rationale) +##### السبب -من أجل جعل `BlocListener` يعمل مع مثيلات `bloc` و `cubit` ، تمت إعادة تسمية -معامل `bloc` إلى `cubit` (بما أن `Cubit` هو الفئة الأساسية). +لجعل `BlocListener` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل +من `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية). **v5.x.x** @@ -1571,12 +1617,12 @@ BlocListener( ) ``` -#### ❗ إعادة تسمية معامل `bloc` في `BlocConsumer` إلى `cubit` +#### ❗ إعادة تسمية المعامل `bloc` في `BlocConsumer` إلى `cubit` -##### الأسباب (Rationale) +##### السبب -من أجل جعل `BlocConsumer` يعمل مع مثيلات `bloc` و `cubit` ، تمت إعادة تسمية -معامل `bloc` إلى `cubit` (بما أن `Cubit` هو الفئة الأساسية). +لجعل `BlocConsumer` يعمل مع كلٍ من مثيلات `bloc` و `cubit`، تم تغيير اسم المعامل +من `bloc` إلى `cubit` (نظرًا لأن `Cubit` هو الفئة الأساسية). **v5.x.x** @@ -1604,17 +1650,17 @@ BlocConsumer( ### `package:bloc` -#### ❗ إزالة `initialState` +#### ❗ تمت إزالة `initialState` -##### الأسباب (Rationale) +##### السبب -كمطور، كان الاضطرار إلى تجاوز `initialState` عند إنشاء bloc يمثل مشكلتين +كمطور، كان الاضطرار إلى تجاوز `initialState` عند إنشاء bloc يسبب مشكلتين رئيسيتين: -- يمكن أن تكون الـ `initialState` للـ bloc ديناميكية ويمكن أيضاً الرجوع إليها في - وقت لاحق (حتى خارج الـ bloc نفسه). بطريقة ما، يمكن اعتبار ذلك تسريباً لمعلومات - الـ bloc الداخلية إلى طبقة واجهة المستخدم. -- إنها طريقة مطولة (verbose). +- يمكن أن تكون `initialState` ديناميكية، كما يمكن الوصول إليها لاحقًا (حتى من + خارج الـ bloc نفسه)، مما قد يُعد تسريبًا لبعض تفاصيله الداخلية إلى طبقة واجهة + المستخدم. +- يتطلب ذلك كتابة كود إضافي وغير ضروري (verbose). **v4.x.x** @@ -1642,16 +1688,16 @@ class CounterBloc extends Bloc { #### ❗ إعادة تسمية `BlocDelegate` إلى `BlocObserver` -##### الأسباب (Rationale) +##### السبب -لم يكن اسم `BlocDelegate` وصفاً دقيقاً للدور الذي تلعبه الفئة. يوحي اسم -`BlocDelegate` بأن الفئة تلعب دوراً نشطاً، بينما في الواقع كان الدور المقصود لـ -`BlocDelegate` هو أن يكون مكوناً سلبياً يراقب ببساطة جميع الـ blocs في التطبيق. +لم يكن اسم `BlocDelegate` يعكس بدقة الدور الحقيقي لهذه الفئة. إذ يوحي الاسم بأن +لها دورًا نشطًا، بينما في الواقع كان الهدف منها أن تكون مكونًا سلبيًا يقوم فقط +بمراقبة جميع الـ blocs داخل التطبيق. :::note -من الناحية المثالية، لا ينبغي التعامل مع أي وظائف أو ميزات موجهة للمستخدم داخل -`BlocObserver`. +من المفترض ألا يحتوي `BlocObserver` على أي منطق أو وظائف موجهة للمستخدم، بل +يقتصر دوره على المراقبة فقط. ::: @@ -1671,16 +1717,16 @@ class MyBlocObserver extends BlocObserver { } ``` -#### ❗ إزالة `BlocSupervisor` +#### ❗ تمت إزالة `BlocSupervisor` -##### الأسباب (Rationale) +##### السبب -كان `BlocSupervisor` مكوناً آخر يتعين على المطورين معرفته والتفاعل معه لغرض وحيد -هو تحديد `BlocDelegate` مخصص. مع التغيير إلى `BlocObserver` ، شعرنا أنه من -الأفضل لتجربة المطور تعيين المراقب مباشرة على الـ bloc نفسه. +كان `BlocSupervisor` مكونًا إضافيًا يتعين على المطورين التعرف عليه والتعامل معه +فقط من أجل تعيين `BlocDelegate` مخصص. مع الانتقال إلى `BlocObserver`، أصبح من +الأبسط والأفضل من ناحية تجربة المطور تعيين المراقب مباشرة على الـ bloc نفسه. -?> مكننا هذا التغيير أيضاً من فصل إضافات bloc الأخرى مثل `HydratedStorage` عن -الـ `BlocObserver`. +?> كما أتاح هذا التغيير فصل بعض إضافات bloc الأخرى مثل `HydratedStorage` عن +`BlocObserver`. **v4.x.x** @@ -1698,10 +1744,10 @@ Bloc.observer = MyBlocObserver(); #### ❗ إعادة تسمية `condition` في `BlocBuilder` إلى `buildWhen` -##### الأسباب (Rationale) +##### السبب -عند استخدام `BlocBuilder` ، كان بإمكاننا سابقاً تحديد `condition` لتحديد ما إذا -كان يجب على الـ `builder` إعادة البناء. +عند استخدام `BlocBuilder`، كان بالإمكان سابقًا تحديد `condition` للتحكم في ما +إذا كان يجب على `builder` إعادة البناء أم لا. ```dart BlocBuilder( @@ -1712,10 +1758,11 @@ BlocBuilder( ) ``` -اسم `condition` ليس واضحاً جداً أو بديهياً، والأهم من ذلك، عند التفاعل مع -`BlocConsumer` ، أصبحت واجهة برمجة التطبيقات غير متسقة لأن المطورين يمكنهم تقديم -شرطين (واحد للـ `builder` وواحد للـ `listener`). ونتيجة لذلك، كشف API الـ -`BlocConsumer` عن `buildWhen` و `listenWhen`. +لم يكن الاسم `condition` واضحًا أو معبّرًا بشكل كافٍ، والأهم من ذلك أنه أدى إلى +عدم اتساق في واجهة الاستخدام (API) عند التعامل مع `BlocConsumer`، حيث يمكن +للمطورين تحديد شرطين مختلفين (أحدهما لـ `builder` والآخر لـ `listener`). + +ونتيجة لذلك، أصبحت واجهة `BlocConsumer` توفر `buildWhen` و `listenWhen`. ```dart BlocConsumer( @@ -1730,8 +1777,8 @@ BlocConsumer( ) ``` -من أجل مواءمة واجهة برمجة التطبيقات وتوفير تجربة مطور أكثر اتساقاً، تمت إعادة -تسمية `condition` إلى `buildWhen`. +لتحقيق اتساق أكبر في واجهة الاستخدام (API) وتوفير تجربة تطوير أكثر سلاسة، تمت +إعادة تسمية `condition` إلى `buildWhen`. **v4.x.x** @@ -1757,9 +1804,10 @@ BlocBuilder( #### ❗ إعادة تسمية `condition` في `BlocListener` إلى `listenWhen` -##### الأسباب (Rationale) +##### السبب -لنفس الأسباب المذكورة أعلاه، تمت أيضاً إعادة تسمية شرط `BlocListener`. +لنفس الأسباب المذكورة أعلاه، تم أيضًا إعادة تسمية `condition` في `BlocListener` +إلى `listenWhen`. **v4.x.x** @@ -1787,14 +1835,14 @@ BlocListener( #### ❗ إعادة تسمية `HydratedStorage` و `HydratedBlocStorage` -##### الأسباب (Rationale) +##### السبب -من أجل تحسين إعادة استخدام الكود بين +لتحسين إعادة استخدام الكود بين [hydrated_bloc](https://pub.dev/packages/hydrated_bloc) و -[hydrated_cubit](https://pub.dev/packages/hydrated_cubit) ، تمت إعادة تسمية -تطبيق التخزين الافتراضي الفعلي من `HydratedBlocStorage` إلى `HydratedStorage`. -بالإضافة إلى ذلك، تمت إعادة تسمية واجهة `HydratedStorage` من `HydratedStorage` -إلى `Storage`. +[hydrated_cubit](https://pub.dev/packages/hydrated_cubit)، تم تغيير اسم تنفيذ +التخزين الافتراضي من `HydratedBlocStorage` إلى `HydratedStorage`. + +بالإضافة إلى ذلك، تمت إعادة تسمية واجهة `HydratedStorage` إلى `Storage`. **v4.0.0** @@ -1814,20 +1862,20 @@ class MyHydratedStorage implements Storage { #### ❗ فصل `HydratedStorage` عن `BlocDelegate` -##### الأسباب (Rationale) +##### السبب -كما ذكرنا سابقاً، تمت إعادة تسمية `BlocDelegate` إلى `BlocObserver` وتم تعيينه -مباشرة كجزء من الـ `bloc` عبر: +كما ذُكر سابقًا، تمت إعادة تسمية `BlocDelegate` إلى `BlocObserver`، وأصبح يتم +تعيينه مباشرة ضمن إعدادات الـ bloc كما يلي: ```dart Bloc.observer = MyBlocObserver(); ``` -تم إجراء التغيير التالي من أجل: +تم إجراء هذا التغيير من أجل: -- البقاء متسقاً مع API مراقب bloc الجديد. -- إبقاء التخزين محصوراً في `HydratedBloc` فقط. -- فصل الـ `BlocObserver` عن الـ `Storage`. +- الحفاظ على الاتساق مع واجهة `BlocObserver` الجديدة +- إبقاء التخزين (`Storage`) محصورًا ضمن `HydratedBloc` فقط +- فصل `BlocObserver` عن `Storage` **v4.0.0** @@ -1843,12 +1891,14 @@ HydratedBloc.storage = await HydratedStorage.build(); #### ❗ تبسيط عملية التهيئة (Initialization) -##### الأسباب (Rationale) +##### السبب + +سابقًا، كان على المطورين استدعاء `super.initialState ?? DefaultInitialState()` +يدويًا من أجل تهيئة مثيلات `HydratedBloc`. كان هذا الأسلوب معقدًا ومطولًا، كما +أنه غير متوافق مع التغييرات الجوهرية على `initialState` في `bloc`. -سابقاً، كان على المطورين استدعاء `super.initialState ?? DefaultInitialState()` -يدوياً من أجل إعداد مثيلات `HydratedBloc` الخاصة بهم. كان هذا الأمر ثقيلاً -ومطولاً وغير متوافق أيضاً مع التغييرات الجذرية في `initialState` في `bloc`. -ونتيجة لذلك، أصبحت تهيئة `HydratedBloc` في v5.0.0 مطابقة لتهيئة `Bloc` العادية. +ونتيجة لذلك، أصبحت تهيئة `HydratedBloc` في الإصدار v5.0.0 مماثلة تمامًا لتهيئة +`Bloc` العادية. **v4.0.0** diff --git a/docs/src/content/docs/ar/modeling-state.mdx b/docs/src/content/docs/ar/modeling-state.mdx index 2702b440c30..930ffcce7fe 100644 --- a/docs/src/content/docs/ar/modeling-state.mdx +++ b/docs/src/content/docs/ar/modeling-state.mdx @@ -6,13 +6,13 @@ description: نظرة عامة على عدة طرق لنمذجة الحالات import ConcreteClassAndStatusEnumSnippet from '~/components/modeling-state/ConcreteClassAndStatusEnumSnippet.astro'; import SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedClassAndSubclassesSnippet.astro'; -هناك العديد من الأساليب المختلفة عندما يتعلق الأمر بهيكلة حالة التطبيق. لكل منها -مزاياه وعيوبه. في هذا القسم، سنلقي نظرة على عدة أساليب، إيجابياتها وسلبياتها، -ومتى يجب استخدام كل منها. +هناك العديد من الأساليب المختلفة عندما يتعلق الأمر بهيكلة حالة التطبيق. لكل +أسلوب مزاياه وعيوبه. في هذا القسم، سنستعرض عدة أساليب، مع إيجابياتها وسلبياتها، +ومتى يُفضّل استخدام كل منها. الأساليب التالية هي مجرد توصيات وهي اختيارية بالكامل. لا تتردد في استخدام أي -أسلوب تفضله. قد تجد أن بعض الأمثلة/التوثيقات لا تتبع هذه الأساليب بشكل أساسي من -أجل التبسيط/الإيجاز. +أسلوب تفضله. قد تلاحظ أن بعض الأمثلة/التوثيقات لا تتبع هذه الأساليب بشكل صارم، +وذلك لأسباب تتعلق بالتبسيط/الإيجاز. :::tip @@ -21,9 +21,9 @@ import SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedC - توسيع `Equatable` من حزمة [`package:equatable`](https://pub.dev/packages/equatable) -- تعيين التعليق التوضيحي `@Data()` للفئة من حزمة +- استخدام التعليق التوضيحي `@Data()` للفئة من حزمة [`package:data_class`](https://pub.dev/packages/data_class) -- تعيين التعليق التوضيحي **@immutable** للفئة من حزمة +- استخدام التعليق التوضيحي `@immutable` للفئة من حزمة [`package:meta`](https://pub.dev/packages/meta) - تنفيذ دالة `copyWith` - استخدام الكلمة المفتاحية `const` للمُنشئات (constructors) @@ -32,72 +32,68 @@ import SealedClassAndSubclassesSnippet from '~/components/modeling-state/SealedC ## الفئة الملموسة وتعداد الحالة (Concrete Class and Status Enum) -يتكون هذا الأسلوب من **فئة ملموسة واحدة** لجميع الحالات جنبًا إلى جنب مع `enum` -يمثل حالات مختلفة. يتم جعل الخصائص قابلة للقيمة الفارغة (nullable) ويتم التعامل -معها بناءً على الحالة الحالية. يعمل هذا الأسلوب بشكل أفضل للحالات التي ليست -حصرية بشكل صارم و/أو تحتوي على الكثير من الخصائص المشتركة. +يتكون هذا الأسلوب من **فئة ملموسة واحدة** لجميع الحالات، إلى جانب `enum` يمثل +حالات/Statuses مختلفة. يتم جعل الخصائص قابلة للقيمة الفارغة (nullable) ويتم +التعامل معها بناءً على الحالة الحالية. يعمل هذا الأسلوب بشكل أفضل للحالات التي +ليست حصرية بشكل صارم و/أو تحتوي على الكثير من الخصائص المشتركة. -#### الإيجابيات (Pros) +#### الإيجابيات -- **بسيط (Simple)**: سهل إدارة فئة واحدة وتعداد حالة (status enum) وجميع الخصائص - يمكن الوصول إليها بسهولة. -- **موجز (Concise)**: يتطلب عمومًا عددًا أقل من أسطر الكود مقارنة بالأساليب - الأخرى. +- **بسيط**: سهل إدارة فئة واحدة وتعداد حالة (status enum)، وجميع الخصائص متاحة + بسهولة. +- **موجز**: يتطلب غالبًا عددًا أقل من أسطر الكود مقارنة بالأساليب الأخرى. -#### السلبيات (Cons) +#### السلبيات - **غير آمن من حيث النوع (Not Type Safe)**: يتطلب التحقق من `status` قبل الوصول - إلى الخصائص. من الممكن إصدار حالة مشوهة (`emit` a malformed state) يمكن أن - تؤدي إلى أخطاء. الخصائص الخاصة بحالات معينة قابلة للقيمة الفارغة، مما قد يكون - مرهقًا للإدارة ويتطلب إما فك التغليف القسري (force unwrapping) أو إجراء فحوصات - القيمة الفارغة (null checks). يمكن التخفيف من بعض هذه السلبيات عن طريق كتابة - اختبارات الوحدة (unit tests) وكتابة مُنشئات مُتخصصة ومُسماة. -- **متضخم (Bloated)**: ينتج عنه حالة واحدة يمكن أن تصبح متضخمة بالعديد من - الخصائص بمرور الوقت. + إلى الخصائص. من الممكن إصدار حالة غير صحيحة (`emit` a malformed state) مما قد + يؤدي إلى أخطاء. كما أن خصائص حالات معينة تكون nullable، وقد يكون التعامل معها + مرهقًا ويتطلب إما فك تغليف قسري (force unwrapping) أو إجراء فحوصات null. يمكن + التخفيف من بعض هذه السلبيات عبر اختبارات الوحدة (unit tests) أو إنشاء مُنشئات + (constructors) مخصصة ومُسماة. +- **متضخم**: ينتج عنه State واحدة قد تصبح متضخمة مع مرور الوقت بسبب كثرة + الخصائص. -#### الحكم (Verdict) +#### الحكم يعمل هذا الأسلوب بشكل أفضل للحالات البسيطة أو عندما تتطلب المتطلبات حالات ليست -حصرية (على سبيل المثال، إظهار شريط رسائل (snackbar) عند حدوث خطأ مع الاستمرار في -عرض البيانات القديمة من آخر حالة نجاح). يوفر هذا الأسلوب المرونة والإيجاز على -حساب أمان النوع. +حصرية (مثل إظهار Snackbar عند حدوث خطأ مع الاستمرار في عرض البيانات القديمة من +آخر حالة نجاح). يوفر مرونة وإيجازًا على حساب أمان النوع. ## الفئة المختومة والفئات الفرعية (Sealed Class and Subclasses) -يتكون هذا الأسلوب من **فئة مختومة (sealed class)** تحمل أي خصائص مشتركة وفئات -فرعية متعددة للحالات المنفصلة. هذا الأسلوب رائع للحالات المنفصلة والحصرية. +يتكون هذا الأسلوب من **فئة مختومة (sealed class)** تحتوي على الخصائص المشتركة، +مع عدة فئات فرعية تمثل الحالات المنفصلة. هذا الأسلوب مناسب جدًا للحالات المنفصلة +والحصرية. -#### الإيجابيات (Pros) +#### الإيجابيات -- **آمن من حيث النوع (Type Safe)**: الكود آمن من حيث التجميع (compile-safe) وليس - من الممكن الوصول عن طريق الخطأ إلى خاصية غير صالحة. تحمل كل فئة فرعية خصائصها - الخاصة، مما يجعل من الواضح أي الخصائص تنتمي إلى أي حالة. -- **صريح (Explicit)**: يفصل الخصائص المشتركة عن الخصائص الخاصة بالحالة. -- **شامل (Exhaustive)**: استخدام عبارة `switch` لفحوصات الشمول (exhaustiveness +- **آمن من حيث النوع (Type Safe)**: الكود آمن وقت التجميع (compile-safe) ولا + يمكن الوصول بالخطأ إلى خاصية غير صالحة. كل فئة فرعية تحتوي خصائصها الخاصة، مما + يجعل من الواضح ما الذي ينتمي لأي حالة. +- **صريح (Explicit)**: يفصل الخصائص المشتركة عن الخصائص الخاصة بكل حالة. +- **شامل (Exhaustive)**: يمكنك استخدام `switch` لفحوصات الشمول (exhaustiveness checks) لضمان التعامل مع كل حالة بشكل صريح. - إذا كنت لا تريد - [التبديل الشامل (exhaustive switching)](https://dart.dev/language/branches#exhaustiveness-checking) - أو تريد أن تكون قادرًا على إضافة أنواع فرعية لاحقًا دون كسر واجهة برمجة - التطبيقات (API)، فاستخدم مُعدِّل + [التبديل الشامل](https://dart.dev/language/branches#exhaustiveness-checking) + أو تريد إضافة أنواع فرعية لاحقًا دون كسر واجهة الـ API، فاستخدم [final](https://dart.dev/language/class-modifiers#final). - - راجع - [توثيق الفئة المختومة (sealed class documentation)](https://dart.dev/language/class-modifiers#sealed) + - راجع [توثيق sealed class](https://dart.dev/language/class-modifiers#sealed) لمزيد من التفاصيل. -#### السلبيات (Cons) +#### السلبيات -- **مطول (Verbose)**: يتطلب المزيد من الكود (فئة أساسية واحدة وفئة فرعية لكل - حالة). قد يتطلب أيضًا كودًا مكررًا للخصائص المشتركة عبر الفئات الفرعية. -- **معقد (Complex)**: تتطلب إضافة خصائص جديدة تحديث كل فئة فرعية والفئة - الأساسية، مما قد يكون مرهقًا ويؤدي إلى زيادة في تعقيد الحالة. بالإضافة إلى - ذلك، قد يتطلب فحوصات نوع غير ضرورية/مفرطة للوصول إلى الخصائص. +- **مطوّل (Verbose)**: يتطلب كودًا أكثر (فئة أساسية + فئة فرعية لكل حالة). وقد + يتطلب تكرارًا للخصائص المشتركة عبر الفئات الفرعية. +- **أكثر تعقيدًا (Complex)**: إضافة خصائص جديدة قد تتطلب تحديث الفئة الأساسية + وجميع الفئات الفرعية، مما يزيد التعقيد. وقد يؤدي أيضًا إلى فحوصات نوع إضافية + للوصول إلى خصائص معينة. -#### الحكم (Verdict) +#### الحكم يعمل هذا الأسلوب بشكل أفضل للحالات المحددة جيدًا والحصرية ذات الخصائص الفريدة. -يوفر هذا الأسلوب أمان النوع وفحوصات الشمول ويؤكد على الأمان على حساب الإيجاز -والبساطة. +يوفر أمان النوع وفحوصات الشمول، ويؤكد على السلامة أكثر من الإيجاز والبساطة. diff --git a/docs/src/content/docs/ar/naming-conventions.mdx b/docs/src/content/docs/ar/naming-conventions.mdx index e67ca2e385e..b8486dece76 100644 --- a/docs/src/content/docs/ar/naming-conventions.mdx +++ b/docs/src/content/docs/ar/naming-conventions.mdx @@ -1,6 +1,6 @@ --- title: اصطلاحات التسمية -description: نظرة عامة على اصطلاحات التسمية الموصى بها عند استخدام حزمة bloc. +description: نظرة عامة على اصطلاحات التسمية الموصى بها عند استخدام bloc. --- import EventExamplesGood1 from '~/components/naming-conventions/EventExamplesGood1Snippet.astro'; @@ -9,26 +9,26 @@ import StateExamplesGood1Snippet from '~/components/naming-conventions/StateExam import SingleStateExamplesGood1Snippet from '~/components/naming-conventions/SingleStateExamplesGood1Snippet.astro'; import StateExamplesBad1Snippet from '~/components/naming-conventions/StateExamplesBad1Snippet.astro'; -اصطلاحات التسمية التالية هي مجرد توصيات وهي **اختيارية بالكامل**. لا تتردد في -استخدام أي اصطلاحات تسمية تفضلها. قد تجد أن بعض الأمثلة/التوثيقات لا تتبع -اصطلاحات التسمية هذه، ويرجع ذلك بشكل أساسي إلى البساطة/الإيجاز. يوصى بشدة بهذه -الاصطلاحات للمشاريع الكبيرة التي تضم عدة مطورين. +اصطلاحات التسمية التالية هي مجرد توصيات وهي اختيارية بالكامل. لا تتردد في +استخدام أي أسلوب تسمية تفضله. قد تلاحظ أن بعض الأمثلة أو أجزاء التوثيق لا تلتزم +بهذه الاصطلاحات، وذلك غالبًا من أجل البساطة أو الإيجاز. ومع ذلك، يُوصى بشدة بهذه +الاصطلاحات في المشاريع الكبيرة التي يعمل عليها عدة مطورين. ## اصطلاحات الأحداث (Event Conventions) -يجب تسمية الأحداث بصيغة **الماضي** لأن الأحداث هي أشياء حدثت بالفعل من منظور الـ -bloc. +يجب أن تُسمّى الأحداث بصيغة **الماضي**، لأن الحدث من منظور الـ bloc هو شيء حدث +بالفعل. -### الهيكلية (Anatomy) +### البنية (Anatomy) `BlocSubject` + `Noun (اختياري)` + `Verb (الحدث)` -يجب أن تتبع أحداث التحميل الأولية الاصطلاح التالي: `BlocSubject` + `Started` +يجب أن تتبع أحداث التحميل الأولي الاصطلاح التالي: `BlocSubject` + `Started` :::note -يجب أن يكون اسم فئة الحدث الأساسية (base event class) هو: `BlocSubject` + -`Event`. +يجب أن يكون اسم فئة الحدث الأساسية (base event class) بالشكل التالي: +`BlocSubject` + `Event`. ::: @@ -44,24 +44,24 @@ bloc. ## اصطلاحات الحالات (State Conventions) -يجب أن تكون الحالات أسماء (Nouns) لأن الحالة هي مجرد لقطة (snapshot) في نقطة -زمنية معينة. هناك طريقتان شائعتان لتمثيل الحالة: استخدام الفئات الفرعية -(subclasses) أو استخدام فئة واحدة (single class). +يجب أن تكون الحالات أسماء (Nouns)، لأن الحالة تمثل مجرد لقطة (snapshot) في نقطة +زمنية محددة. هناك طريقتان شائعتان لتمثيل الحالة: باستخدام فئات فرعية +(subclasses) أو باستخدام فئة واحدة (single class). -### الهيكلية (Anatomy) +### البنية (Anatomy) #### الفئات الفرعية (Subclasses) `BlocSubject` + `Verb (الإجراء)` + `State` -عند تمثيل الحالة كفئات فرعية متعددة، يجب أن تكون `State` واحدة مما يلي: +عند تمثيل الحالة باستخدام فئات فرعية متعددة، يجب أن تكون `State` واحدة من القيم +التالية: `Initial` | `Success` | `Failure` | `InProgress` :::note -يجب أن تتبع الحالات الأولية (Initial states) الاصطلاح التالي: `BlocSubject` + -`Initial`. +يجب أن تتبع الحالات الأولية الاصطلاح التالي: `BlocSubject` + `Initial`. ::: @@ -69,10 +69,10 @@ bloc. `BlocSubject` + `State` -عند تمثيل الحالة كفئة أساسية واحدة، يجب استخدام تعداد (enum) باسم +عند تمثيل الحالة كفئة أساسية واحدة، يجب استخدام تعداد (enum) باسم: `BlocSubject` + `Status` لتمثيل حالة الـ State: -`initial` | `success` | `failure` | `loading`. +`initial` | `success` | `failure` | `loading` :::note diff --git a/docs/src/content/docs/ar/testing.mdx b/docs/src/content/docs/ar/testing.mdx index 899dd1cef5a..4fa28c3ffbf 100644 --- a/docs/src/content/docs/ar/testing.mdx +++ b/docs/src/content/docs/ar/testing.mdx @@ -1,6 +1,6 @@ --- title: الاختبار (Testing) -description: أساسيات كيفية كتابة الاختبارات للـ blocs الخاصة بك. +description: أساسيات كيفية كتابة اختبارات للـ blocs الخاصة بك. --- import CounterBlocSnippet from '~/components/testing/CounterBlocSnippet.astro'; @@ -11,46 +11,45 @@ import CounterBlocTestSetupSnippet from '~/components/testing/CounterBlocTestSet import CounterBlocTestInitialStateSnippet from '~/components/testing/CounterBlocTestInitialStateSnippet.astro'; import CounterBlocTestBlocTestSnippet from '~/components/testing/CounterBlocTestBlocTestSnippet.astro'; -تم تصميم Bloc ليكون من السهل جداً اختباره. في هذا القسم، سنستعرض كيفية إجراء -اختبار الوحدة (unit test) للـ bloc. +تم تصميم Bloc ليكون سهل الاختبار إلى حدٍ كبير. في هذا القسم، سنستعرض كيفية كتابة +اختبارات وحدة (unit tests) للـ bloc. -من أجل التبسيط، دعونا نكتب اختبارات لـ `CounterBloc` الذي أنشأناه في +لأغراض التبسيط، سنقوم بكتابة اختبارات لـ `CounterBloc` الذي أنشأناه في [المفاهيم الأساسية](/ar/bloc-concepts). -للتذكير، يبدو تطبيق `CounterBloc` كالتالي: +للتذكير، فإن تنفيذ `CounterBloc` يبدو كالتالي: ## الإعداد (Setup) -قبل أن نبدأ في كتابة اختباراتنا، سنحتاج إلى إضافة إطار عمل للاختبار (testing -framework) إلى تبعيات المشروع. +قبل البدء في كتابة الاختبارات، نحتاج إلى إضافة إطار عمل للاختبار إلى تبعيات +المشروع. -نحتاج إلى إضافة [test](https://pub.dev/packages/test) و +نحتاج إلى إضافة حزمتَي [test](https://pub.dev/packages/test) و [bloc_test](https://pub.dev/packages/bloc_test) إلى مشروعنا. ## الاختبار (Testing) -لنبدأ بإنشاء ملف لاختبارات `CounterBloc` ، باسم `counter_bloc_test.dart` -واستيراد حزمة الاختبار. +لنبدأ بإنشاء ملف لاختبارات `CounterBloc` باسم `counter_bloc_test.dart` واستيراد +حزمة الاختبار. -بعد ذلك، نحتاج إلى إنشاء دالة `main` بالإضافة إلى مجموعة الاختبار (test group). +بعد ذلك، نحتاج إلى إنشاء دالة `main` بالإضافة إلى مجموعة اختبارات (test group). :::note -تُستخدم المجموعات (Groups) لتنظيم الاختبارات الفردية بالإضافة إلى إنشاء سياق -يمكنك من خلاله مشاركة `setUp` و `tearDown` مشتركين عبر جميع الاختبارات الفردية. +تُستخدم المجموعات (Groups) لتنظيم الاختبارات الفردية، وكذلك لإنشاء سياق يمكن من +خلاله مشاركة دوال `setUp` و `tearDown` بين جميع الاختبارات داخل المجموعة. ::: -لنبدأ بإنشاء مثيل من `CounterBloc` الخاص بنا والذي سيتم استخدامه عبر جميع -اختباراتنا. +لنبدأ بإنشاء مثيل من `CounterBloc` سيتم استخدامه عبر جميع اختباراتنا. @@ -60,20 +59,20 @@ framework) إلى تبعيات المشروع. :::note -يمكننا تشغيل جميع اختباراتنا باستخدام الأمر `dart test`. +يمكن تشغيل جميع الاختبارات باستخدام الأمر `dart test`. ::: -في هذه المرحلة، يجب أن يكون لدينا أول اختبار ناجح! الآن دعونا نكتب اختباراً أكثر -تعقيداً باستخدام حزمة [bloc_test](https://pub.dev/packages/bloc_test). +في هذه المرحلة، يجب أن يكون لدينا أول اختبار ناجح! الآن دعونا نكتب اختبارًا أكثر +تقدمًا باستخدام حزمة [bloc_test](https://pub.dev/packages/bloc_test). -يجب أن نكون قادرين على تشغيل الاختبارات ورؤية أن جميعها ناجحة. +يجب أن نتمكن من تشغيل الاختبارات والتأكد من أن جميعها ناجحة. -هذا كل ما في الأمر، يجب أن يكون الاختبار سهلاً للغاية ويجب أن نشعر بالثقة عند -إجراء التغييرات وإعادة هيكلة الكود الخاص بنا. +بهذا نكون قد انتهينا. ينبغي أن يكون الاختبار أمرًا سهلًا، وأن نشعر بالثقة عند +إجراء التعديلات أو إعادة هيكلة الكود. يمكنك الرجوع إلى -[تطبيق الطقس (Weather App)](https://github.com/felangel/bloc/tree/master/examples/flutter_weather) -للحصول على مثال لتطبيق مختبر بالكامل. +[تطبيق Weather App](https://github.com/felangel/bloc/tree/master/examples/flutter_weather) +للاطلاع على مثال لتطبيق مختبَر بالكامل. diff --git a/docs/src/content/docs/ar/tutorials/flutter-counter.mdx b/docs/src/content/docs/ar/tutorials/flutter-counter.mdx index 0af5ea05368..f5626542caa 100644 --- a/docs/src/content/docs/ar/tutorials/flutter-counter.mdx +++ b/docs/src/content/docs/ar/tutorials/flutter-counter.mdx @@ -1,7 +1,7 @@ --- title: Flutter Counter description: - دليل متعمق حول كيفية بناء تطبيق عداد (Counter) باستخدام فلاتر ومكتبة Bloc. + دليل متعمّق لبناء تطبيق عدّاد (Counter) في Flutter باستخدام مكتبة Bloc. sidebar: order: 1 --- @@ -12,8 +12,8 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as ![beginner](https://img.shields.io/badge/level-beginner-green.svg) -في هذا الدليل التعليمي، سنقوم ببناء تطبيق **عداد (Counter)** في فلاتر باستخدام -مكتبة **Bloc**. +في هذا الدليل التعليمي، سنبني تطبيق **عداد (Counter)** في Flutter باستخدام مكتبة +**Bloc**. ![demo](~/assets/tutorials/flutter-counter.gif) @@ -21,10 +21,10 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as - مراقبة تغييرات الحالة باستخدام [`BlocObserver`](/ar/bloc-concepts#blocobserver). -- [`BlocProvider`](/ar/flutter-bloc-concepts#blocprovider)، وهي ويدجت (Widget) - من فلاتر توفر Bloc لأبنائها. -- [`BlocBuilder`](/ar/flutter-bloc-concepts#blocbuilder)، وهي ويدجت من فلاتر - تتولى بناء الويدجت استجابةً للحالات الجديدة. +- [`BlocProvider`](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في + Flutter توفّر Bloc للأبناء. +- [`BlocBuilder`](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter + تتولّى إعادة البناء استجابةً للحالات الجديدة. - استخدام Cubit بدلاً من Bloc. [ما هو الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc) - إضافة الأحداث باستخدام @@ -32,7 +32,7 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as ## الإعداد (Setup) -سنبدأ بإنشاء مشروع فلاتر جديد تمامًا: +سنبدأ بإنشاء مشروع Flutter جديد بالكامل: @@ -43,7 +43,7 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as title="pubspec.yaml" /> -ثم نقوم بتثبيت جميع التبعيات (Dependencies) الخاصة بنا: +ثم نثبّت جميع التبعيات (dependencies): @@ -66,49 +66,49 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as ├── pubspec.yaml ``` -يستخدم التطبيق هيكل دليل (Directory Structure) يعتمد على الميزات -(Feature-driven). يتيح لنا هيكل المشروع هذا توسيع نطاق المشروع من خلال وجود -ميزات مكتفية ذاتيًا. في هذا المثال، سيكون لدينا ميزة واحدة فقط (العداد نفسه)، -ولكن في التطبيقات الأكثر تعقيدًا، يمكن أن يكون لدينا المئات من الميزات المختلفة. +يستخدم التطبيق هيكل مجلدات يعتمد على الميزات (feature-driven directory +structure). هذا النمط يساعدنا على توسيع المشروع عبر ميزات مستقلة بذاتها. في هذا +المثال لدينا ميزة واحدة فقط (العداد نفسه)، لكن في التطبيقات الأكثر تعقيدًا قد +نمتلك مئات الميزات المختلفة. ## BlocObserver -أول شيء سننظر إليه هو كيفية إنشاء `BlocObserver`، والذي سيساعدنا في مراقبة جميع -تغييرات الحالة في التطبيق. +أول ما سنراجعه هو إنشاء `BlocObserver` الذي يساعدنا على مراقبة جميع تغييرات +الحالة في التطبيق. -لنقم بإنشاء الملف `lib/counter_observer.dart`: +لننشئ الملف `lib/counter_observer.dart`: -في هذه الحالة، نحن نقوم فقط بتجاوز الدالة `onChange` لرؤية جميع تغييرات الحالة -التي تحدث. +في هذه الحالة، نقوم فقط بعمل override للدالة `onChange` لمتابعة جميع تغييرات +الحالة. :::note -تعمل الدالة `onChange` بنفس الطريقة لكل من نُسخ `Bloc` و `Cubit`. +تعمل الدالة `onChange` بالطريقة نفسها في instances من `Bloc` و`Cubit`. ::: ## main.dart -بعد ذلك، لنقم باستبدال محتويات الملف `lib/main.dart` بما يلي: +بعد ذلك، استبدل محتويات الملف `lib/main.dart` بما يلي: -نقوم بتهيئة `CounterObserver` الذي أنشأناه للتو واستدعاء `runApp` باستخدام ويدجت -`CounterApp`، والذي سننظر إليه لاحقًا. +هنا نهيّئ `CounterObserver` الذي أنشأناه للتو، ثم نستدعي `runApp` باستخدام +Widget `CounterApp` التي سنراجعها الآن. ## تطبيق العداد (Counter App) -لنقم بإنشاء الملف `lib/app.dart`: +لننشئ الملف `lib/app.dart`: -سيكون `CounterApp` عبارة عن `MaterialApp` ويحدد `home` على أنه `CounterPage`. +`CounterApp` هو `MaterialApp` ويحدد `home` على أنه `CounterPage`. -يتم استخدام `BlocBuilder` لتغليف ويدجت `Text` من أجل تحديث النص في أي وقت تتغير -فيه حالة `CounterCubit`. بالإضافة إلى ذلك، يتم استخدام -`context.read()` للبحث عن أقرب نسخة من `CounterCubit`. +نستخدم `BlocBuilder` لتغليف Widget `Text` بهدف تحديث النص كلما تغيّرت حالة +`CounterCubit`. بالإضافة إلى ذلك، نستخدم `context.read()` للعثور +على أقرب instance من `CounterCubit`. :::note -يتم تغليف ويدجت `Text` فقط في `BlocBuilder` لأنها الويدجت الوحيدة التي تحتاج إلى -إعادة بنائها استجابةً لتغييرات الحالة في `CounterCubit`. تجنب تغليف الويدجت التي -لا تحتاج إلى إعادة بناء عند تغيير الحالة دون داعٍ. +تم تغليف Widget `Text` فقط داخل `BlocBuilder` لأنها الوحيدة التي تحتاج إعادة +بناء عند تغيّر حالة `CounterCubit`. تجنّب تغليف Widgets لا تحتاج لإعادة البناء +عند تغيّر الحالة. ::: ## Barrel (تجميع الصادرات) -لنقم بإنشاء الملف `lib/counter/view/view.dart`: +لننشئ الملف `lib/counter/view/view.dart`: -أضف `view.dart` لتصدير جميع الأجزاء العامة (Public) لعرض العداد. +أضف `view.dart` لتصدير جميع الأجزاء العامة (public) الخاصة بعرض العداد. -لنقم بإنشاء الملف `lib/counter/counter.dart`: +لننشئ الملف `lib/counter/counter.dart`: -أضف `counter.dart` لتصدير جميع الأجزاء العامة لميزة العداد. +أضف `counter.dart` لتصدير جميع الأجزاء العامة (public) الخاصة بميزة العداد. -هذا كل شيء! لقد قمنا بفصل طبقة العرض (Presentation Layer) عن طبقة منطق الأعمال -(Business Logic Layer). لا تملك `CounterView` أي فكرة عما يحدث عندما يضغط -المستخدم على زر؛ إنها فقط تخطر `CounterCubit`. علاوة على ذلك، لا يملك -`CounterCubit` أي فكرة عما يحدث مع الحالة (قيمة العداد)؛ إنه ببساطة يصدر حالات -جديدة استجابةً لاستدعاء الطرق (Methods). +انتهينا! قمنا بفصل طبقة العرض (presentation layer) عن طبقة منطق الأعمال +(business logic layer). لا تعرف `CounterView` ماذا يحدث عند ضغط المستخدم على +الزر؛ هي فقط تُخطر `CounterCubit`. وفي المقابل، لا يعرف `CounterCubit` شيئًا عن +طريقة عرض الحالة (قيمة العداد)، بل يصدر حالات جديدة استجابةً لاستدعاء methods. -يمكننا تشغيل تطبيقنا باستخدام الأمر `flutter run` وعرضه على جهازنا أو -المحاكي/المحاكي. +يمكننا تشغيل التطبيق بالأمر `flutter run` وعرضه على الجهاز أو +simulator/emulator. -يمكن العثور على المصدر الكامل (بما في ذلك اختبارات الوحدة والويدجت) لهذا المثال +يمكن العثور على المصدر الكامل (بما في ذلك اختبارات الوحدة واختبارات Widgets) +لهذا المثال [هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_counter). diff --git a/docs/src/content/docs/ar/tutorials/flutter-firebase-login.mdx b/docs/src/content/docs/ar/tutorials/flutter-firebase-login.mdx index 26dee9823dc..50c5f1745d8 100644 --- a/docs/src/content/docs/ar/tutorials/flutter-firebase-login.mdx +++ b/docs/src/content/docs/ar/tutorials/flutter-firebase-login.mdx @@ -1,8 +1,8 @@ --- -title: تسجيل الدخول باستخدام Flutter و Firebase +title: تسجيل الدخول باستخدام Flutter وFirebase description: - دليل متعمق حول كيفية بناء تدفق تسجيل دخول في Flutter باستخدام مكتبتي Bloc و - Firebase. + دليل متعمق لبناء تدفق تسجيل دخول (Login Flow) في Flutter باستخدام Bloc + وFirebase. sidebar: order: 7 --- @@ -13,26 +13,26 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as ![advanced](https://img.shields.io/badge/level-advanced-red.svg) -في هذا الدليل، سنقوم ببناء **تدفق تسجيل دخول باستخدام Firebase في Flutter** -بالاستعانة بمكتبة **Bloc**. +في هذا الدليل، سنبني تدفق تسجيل دخول (Firebase Login Flow) في Flutter باستخدام +مكتبة Bloc. ![demo](~/assets/tutorials/flutter-firebase-login.gif) ## المواضيع الرئيسية -- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي ويدجت (Widget) من - Flutter توفر كتلة (Bloc) لأبنائها. -- استخدام Cubit بدلاً من Bloc. - [ما هو الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc) -- إضافة الأحداث باستخدام [context.read](/ar/flutter-bloc-concepts#contextread). -- منع إعادة البناء غير الضرورية باستخدام +- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهو `widget` من + Flutter يوفّر Bloc للأبناء. +- استخدام Cubit بدلًا من Bloc. [ما الفرق؟](/ar/bloc-concepts/#cubit-مقابل-bloc) +- إضافة الأحداث (events) باستخدام + [context.read](/ar/flutter-bloc-concepts#contextread). +- تجنب إعادة البناء غير الضرورية باستخدام [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable). -- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي ويدجت - من Flutter توفر مستودع (Repository) لأبنائها. -- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي ويدجت من Flutter - تستدعي كود المستمع (listener) استجابةً لتغيرات الحالة في الكتلة (Bloc). -- إضافة الأحداث باستخدام - [context.read](/ar/flutter-bloc-concepts#contextselect). +- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهو + `widget` من Flutter يوفّر Repository للأبناء. +- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهو `widget` من + Flutter يستدعي منطق `listener` عند تغيّر الحالة (state) في Bloc. +- قراءة جزء محدد من الحالة باستخدام + [context.select](/ar/flutter-bloc-concepts#contextselect). ## الإعداد @@ -40,21 +40,23 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as -تمامًا كما في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، سنقوم بإنشاء حزم -داخلية لتقسيم بنية تطبيقنا بشكل أفضل والحفاظ على حدود واضحة، ولزيادة قابلية -إعادة الاستخدام وقابلية الاختبار. +وكما فعلنا في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، سننشئ حزمًا +داخلية لتقسيم معمارية التطبيق (application architecture) إلى طبقات أوضح، مع +الحفاظ على حدود واضحة بين الطبقات، وتحسين قابلية إعادة الاستخدام (reusability) +والاختبار (testability). -في هذه الحالة، ستكون حزمتا +في هذا المثال، ستكون حزمتا [firebase_auth](https://pub.dev/packages/firebase_auth) و [google_sign_in](https://pub.dev/packages/google_sign_in) هما طبقة البيانات -لدينا، لذا سنقوم بإنشاء `AuthenticationRepository` فقط لتجميع البيانات من عميلي -(clients) واجهة برمجة التطبيقات (API) هذين. +(data layer). لذلك سننشئ `AuthenticationRepository` فقط لدمج البيانات من عميلَي +واجهات البرمجة (API clients). ## مستودع المصادقة (Authentication Repository) -سيكون `AuthenticationRepository` مسؤولاً عن تجريد تفاصيل التنفيذ الداخلية لكيفية -مصادقة المستخدم وجلب معلوماته. في هذه الحالة، سيتم التكامل مع Firebase، ولكن -يمكننا دائمًا تغيير التنفيذ الداخلي لاحقًا ولن يتأثر تطبيقنا. +سيكون `AuthenticationRepository` مسؤولًا عن إخفاء تفاصيل التنفيذ الداخلية +(implementation details) لطريقة مصادقة المستخدم وجلب بياناته. في هذا المثال +سيتكامل مع Firebase، لكن يمكننا لاحقًا تغيير التنفيذ الداخلي دون التأثير على +باقي التطبيق. ### الإعداد @@ -66,15 +68,15 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as title="packages/authentication_repository/pubspec.yaml" /> -بعد ذلك، يمكننا تثبيت التبعيات عن طريق تشغيل: +بعد ذلك، يمكننا تثبيت dependencies عبر تشغيل: -في مجلد `authentication_repository`. +داخل مجلد `authentication_repository`. -تمامًا مثل معظم الحزم، سيحدد `authentication_repository` واجهة برمجة التطبيقات -الخاصة به عبر -`packages/authentication_repository/lib/authentication_repository.dart` +مثل أغلب الحزم، يعرّف `authentication_repository` واجهته العامة (API surface) +عبر الملف +`packages/authentication_repository/lib/authentication_repository.dart`. -يكشف `AuthenticationRepository` عن `Stream` يمكننا الاشتراك فيه لتلقي -إشعارات عندما يتغير `User`. بالإضافة إلى ذلك، فإنه يكشف عن طرق لـ `signUp` و -`logInWithGoogle` و `logInWithEmailAndPassword` و `logOut`. +يوفّر `AuthenticationRepository` تدفقًا `Stream` يمكننا الاشتراك فيه +للحصول على إشعار عند تغيّر `User`. كما يوفّر methods مثل `signUp` و +`logInWithGoogle` و`logInWithEmailAndPassword` و`logOut`. :::note -إن `AuthenticationRepository` مسؤول أيضًا عن التعامل مع الأخطاء منخفضة المستوى -التي يمكن أن تحدث في طبقة البيانات ويكشف عن مجموعة نظيفة وبسيطة من الأخطاء التي -تتوافق مع نطاق العمل. +`AuthenticationRepository` مسؤول أيضًا عن التعامل مع أخطاء طبقة البيانات (data +layer) منخفضة المستوى، ويقدّم مجموعة أخطاء أبسط وأنظف ومتوافقة مع النطاق +(domain). ::: -هذا كل شيء بالنسبة لـ `AuthenticationRepository`. بعد ذلك، دعونا نلقي نظرة على -كيفية دمجه في مشروع Flutter الذي أنشأناه. +هذا كل ما نحتاجه في `AuthenticationRepository`. الخطوة التالية هي دمجه في مشروع +Flutter الذي أنشأناه. ## إعداد Firebase نحتاج إلى اتباع -[تعليمات استخدام firebase_auth](https://pub.dev/packages/firebase_auth#usage) من -أجل ربط تطبيقنا بـ Firebase وتمكين +[تعليمات استخدام firebase_auth](https://pub.dev/packages/firebase_auth#usage) +لتوصيل التطبيق مع Firebase وتفعيل [google_sign_in](https://pub.dev/packages/google_sign_in). :::caution -تذكر تحديث `google-services.json` على Android و `GoogleService-Info.plist` و -`Info.plist` على iOS، وإلا سيتعطل التطبيق. +تأكد من تحديث `google-services.json` على Android، و`GoogleService-Info.plist` +و`Info.plist` على iOS، وإلا سيتعطل التطبيق. ::: ## تبعيات المشروع (Project Dependencies) -يمكننا استبدال ملف `pubspec.yaml` الذي تم إنشاؤه في جذر المشروع بما يلي: +يمكننا استبدال ملف `pubspec.yaml` المُولّد في جذر المشروع بما يلي: -لاحظ أننا نحدد مجلد الأصول (assets) لجميع الأصول المحلية لتطبيقاتنا. قم بإنشاء -مجلد `assets` في جذر مشروعك وأضف أصل -[شعار bloc](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png) -(الذي سنستخدمه لاحقًا). +لاحظ أننا نحدد مجلد `assets` لكل الأصول المحلية (local assets) الخاصة بالتطبيق. +أنشئ مجلد `assets` في جذر المشروع، ثم أضف أصل شعار bloc +[bloc logo](https://github.com/felangel/bloc/blob/master/examples/flutter_firebase_login/assets/bloc_logo_small.png) +(سنستخدمه لاحقًا). -ثم قم بتثبيت جميع التبعيات: +بعد ذلك ثبّت جميع dependencies: :::note -نحن نعتمد على حزمة `authentication_repository` عبر المسار (path)، مما سيسمح لنا -بالتكرار بسرعة مع الحفاظ على فصل واضح. +نحن نستخدم حزمة `authentication_repository` عبر `path`، وهذا يتيح لنا التطوير +بسرعة مع الحفاظ على فصل واضح بين الطبقات. ::: @@ -191,39 +193,38 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as title="lib/main.dart" /> -إنه يقوم ببساطة بإعداد بعض التكوينات العامة للتطبيق واستدعاء `runApp` مع مثيل من -`App`. +الملف يضبط إعدادات عامة للتطبيق، ثم يستدعي `runApp` مع نسخة من `App`. :::note -نحن نقوم بحقن مثيل واحد من `AuthenticationRepository` في `App` وهو تبعية صريحة -للمنشئ (constructor dependency). +نحقن (inject) نسخة واحدة من `AuthenticationRepository` داخل `App` كاعتمادية +صريحة في المُنشئ (constructor dependency). ::: ## التطبيق (App) -تمامًا كما في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، سيوفر ملف -`app.dart` الخاص بنا مثيلاً من `AuthenticationRepository` للتطبيق عبر -`RepositoryProvider`، كما يقوم بإنشاء وتوفير مثيل من `AuthenticationBloc`. بعد -ذلك، تستهلك `AppView` كتلة `AuthenticationBloc` وتتولى تحديث المسار الحالي بناءً -على `AuthenticationState`. +كما في [دليل تسجيل الدخول](/ar/tutorials/flutter-login)، يوفّر `app.dart` نسخة +`AuthenticationRepository` للتطبيق عبر `RepositoryProvider`، كما ينشئ نسخة من +`AuthenticationBloc` ويجعلها متاحة. بعد ذلك، تتعامل `AppView` مع +`AuthenticationBloc` وتحدّث المسار الحالي (current route) بناءً على +`AuthenticationState`. -## كتلة التطبيق (App Bloc) +## App Bloc -إن `AppBloc` مسؤول عن إدارة الحالة العامة للتطبيق. لديه تبعية على -`AuthenticationRepository` ويشترك في دفق `user` من أجل إصدار حالات جديدة -استجابةً للتغييرات في المستخدم الحالي. +`AppBloc` مسؤول عن إدارة الحالة العامة (global state) للتطبيق. وهو يعتمد على +`AuthenticationRepository`، ويشترك في تدفق `user` لإصدار حالات جديدة عند تغيّر +المستخدم الحالي. ### الحالة (State) -تتكون `AppState` من `AppStatus` و `User`. يقبل المنشئ الافتراضي `User` اختياريًا -ويعيد التوجيه إلى المنشئ الخاص مع حالة المصادقة المناسبة. +تتكوّن `AppState` من `AppStatus` و`User`. يقبل المُنشئ الافتراضي `User` +اختياريًا، ثم يعيد التوجيه إلى المُنشئ الخاص مع حالة المصادقة المناسبة. -### الكتلة (Bloc) +### Bloc -في جسم المنشئ (constructor body)، يتم تعيين الفئات الفرعية لـ `AppEvent` -لمعالجات الأحداث المقابلة لها. +داخل المُنشئ (constructor body)، يتم ربط الفئات الفرعية من `AppEvent` مع معالجات +الأحداث (event handlers) المقابلة لها. -في معالج الحدث `_onUserSubscriptionRequested`، يستخدم `AppBloc` الدالة -`emit.onEach` للاشتراك في دفق المستخدم الخاص بـ `AuthenticationRepository` -وإصدار حالة استجابةً لكل `User`. +داخل معالج `_onUserSubscriptionRequested`، يستخدم `AppBloc` الدالة `emit.onEach` +للاشتراك في تدفق المستخدم الخاص بـ `AuthenticationRepository`، ثم إصدار حالة لكل +`User` جديد. -تقوم `emit.onEach` بإنشاء اشتراك دفق داخليًا وتتولى إلغاءه عند إغلاق `AppBloc` -أو دفق المستخدم. +`emit.onEach` تنشئ اشتراكًا داخليًا في التدفق (stream subscription)، وتتكفل +بإلغائه عند إغلاق `AppBloc` أو إغلاق تدفق المستخدم. -إذا أصدر دفق المستخدم خطأً، فإن `addError` يعيد توجيه الخطأ وتتبع المكدس (stack -trace) إلى أي `BlocObserver` يستمع. +إذا أصدر تدفق المستخدم خطأً، تقوم `addError` بتمرير الخطأ و`stack trace` إلى أي +`BlocObserver` يستمع. :::caution -إذا تم حذف `onError`، فسيتم اعتبار أي أخطاء في دفق المستخدم غير معالجة، وسيتم -طرحها بواسطة `onEach`. ونتيجة لذلك، سيتم إلغاء الاشتراك في دفق المستخدم. +إذا لم يتم تمرير `onError`، فسيتم اعتبار أي خطأ في تدفق المستخدم غير مُعالَج، +وسيتم رميه من `onEach`. ونتيجة لذلك سيتم إلغاء الاشتراك في تدفق المستخدم. ::: :::tip -يعد [`BlocObserver`](/ar/bloc-concepts/#blocobserver) رائعًا لتسجيل أحداث Bloc -والأخطاء وتغييرات الحالة، خاصة في سياق التحليلات وتقارير الأعطال. +[`BlocObserver`](/ar/bloc-concepts/#blocobserver) مفيد جدًا لتسجيل أحداث Bloc +والأخطاء وتغيّرات الحالة، خصوصًا في سياق التحليلات (analytics) وتقارير الأعطال +(crash reporting). ::: @@ -279,13 +280,12 @@ trace) إلى أي `BlocObserver` يستمع. ## النماذج (Models) -يعد نموذج إدخال `Email` و `Password` مفيدًا لتغليف منطق التحقق من الصحة -(validation logic) وسيتم استخدامه في كل من `LoginForm` و `SignUpForm` (في وقت -لاحق من الدليل). +نماذج إدخال `Email` و`Password` مفيدة لتغليف منطق التحقق (validation logic)، +وسيتم استخدامها في `LoginForm` و`SignUpForm` (لاحقًا في هذا الدليل). -يتم إنشاء كلا نموذجي الإدخال باستخدام حزمة -[formz](https://pub.dev/packages/formz) ويسمحان لنا بالعمل مع كائن تم التحقق من -صحته بدلاً من نوع بدائي مثل `String`. +كلا النموذجين مبنيّ باستخدام حزمة [formz](https://pub.dev/packages/formz)، وهذا +يسمح لنا بالتعامل مع كائن مُتحقَّق منه (validated object) بدلًا من نوع بدائي +(primitive type) مثل `String`. ### البريد الإلكتروني (Email) @@ -303,7 +303,7 @@ trace) إلى أي `BlocObserver` يستمع. ## صفحة تسجيل الدخول (Login Page) -إن `LoginPage` مسؤولة عن إنشاء وتوفير مثيل من `LoginCubit` إلى `LoginForm`. +`LoginPage` مسؤولة عن إنشاء نسخة من `LoginCubit` وتوفيرها إلى `LoginForm`. -يعرض `LoginForm` أيضًا زر "إنشاء حساب" (Create Account) الذي ينتقل إلى -`SignUpPage` حيث يمكن للمستخدم إنشاء حساب جديد تمامًا. +كما يعرض `LoginForm` زر "إنشاء حساب" (Create Account) الذي ينقل المستخدم إلى +`SignUpPage` لإنشاء حساب جديد. -## صفحة التسجيل (Sign Up Page) +## صفحة إنشاء الحساب (Sign Up Page) -يعكس هيكل `SignUp` هيكل `Login` ويتكون من `SignUpPage` و `SignUpView` و +بنية `SignUp` تعكس بنية `Login`، وتتكوّن من `SignUpPage` و`SignUpView` و `SignUpCubit`. -إن `SignUpPage` مسؤولة فقط عن إنشاء وتوفير مثيل من `SignUpCubit` إلى -`SignUpForm` (تمامًا كما في `LoginPage`). +`SignUpPage` مسؤولة فقط عن إنشاء نسخة من `SignUpCubit` وتوفيرها إلى `SignUpForm` +(تمامًا كما في `LoginPage`). -## نموذج التسجيل (Sign Up Form) +## نموذج إنشاء الحساب (Sign Up Form) -إن `SignUpForm` مسؤول عن عرض النموذج استجابةً لـ `SignUpState` ويستدعي طرقًا على +`SignUpForm` مسؤول عن عرض النموذج بناءً على `SignUpState`، ويستدعي methods على `SignUpCubit` استجابةً لتفاعلات المستخدم. -يمكننا بعد ذلك المضي قدمًا واستبدال محتويات `pubspec.yaml` بما يلي: +بعد ذلك، يمكننا استبدال محتويات `pubspec.yaml` بما يلي: -ثم نقوم بتثبيت جميع التبعيات لدينا: +ثم نثبّت جميع التبعيات (dependencies): @@ -77,25 +77,24 @@ import PostBlocTransformerSnippet from '~/components/tutorials/flutter-infinite- ├── pubspec.yaml ``` -يستخدم التطبيق هيكل دليل يعتمد على الميزات (feature-driven directory structure). -يتيح لنا هيكل المشروع هذا توسيع نطاق المشروع من خلال وجود ميزات مكتفية ذاتيًا. -في هذا المثال، سيكون لدينا ميزة واحدة فقط (ميزة المنشورات `post feature`) وهي -مقسمة إلى مجلدات خاصة بها مع ملفات تجميع (barrel files)، المشار إليها بعلامة -النجمة (\*). +يستخدم التطبيق هيكل مجلدات يعتمد على الميزات (feature-driven directory +structure). يتيح لنا هذا النمط توسيع المشروع عبر ميزات مستقلة بذاتها. في هذا +المثال، سيكون لدينا ميزة واحدة فقط (ميزة المنشورات `post feature`) وهي مقسمة إلى +مجلدات خاصة بها مع ملفات تجميع (barrel files)، المشار إليها بعلامة النجمة (\*). ## واجهة برمجة تطبيقات REST -بالنسبة لهذا التطبيق التجريبي، سنستخدم +في هذا التطبيق التجريبي، سنستخدم [jsonplaceholder](http://jsonplaceholder.typicode.com) كمصدر للبيانات. :::note -jsonplaceholder هي واجهة برمجة تطبيقات REST عبر الإنترنت تقدم بيانات وهمية؛ وهي -مفيدة جدًا لبناء النماذج الأولية. +`jsonplaceholder` هي REST API عبر الإنترنت تقدم بيانات وهمية؛ وهي مفيدة جدًا +لبناء النماذج الأولية. ::: -افتح علامة تبويب جديدة في متصفحك وقم بزيارة +افتح علامة تبويب جديدة في المتصفح وزر الرابط التالي: `https://jsonplaceholder.typicode.com/posts?_start=0&_limit=2` لترى ما ترجعه واجهة برمجة التطبيقات. @@ -103,70 +102,68 @@ jsonplaceholder هي واجهة برمجة تطبيقات REST عبر الإنت :::note -في عنوان URL الخاص بنا، حددنا البداية (`start`) والحد (`limit`) كمعلمات استعلام -(query parameters) لطلب GET. +في URL هذا، حددنا `start` و`limit` كمعاملات استعلام (query parameters) في طلب +GET. ::: -رائع، الآن بعد أن عرفنا كيف ستبدو بياناتنا، دعنا ننشئ النموذج (Model). +ممتاز، بعد أن عرفنا شكل البيانات، لننشئ الـ model. ## نموذج البيانات (Data Model) -قم بإنشاء `post.dart` ودعنا نبدأ العمل على إنشاء نموذج كائن المنشور (`Post`). +أنشئ `post.dart` ولنبدأ ببناء model كائن المنشور (`Post`). -`Post` هو مجرد صنف يحتوي على `id` و `title` و `body`. +`Post` عبارة عن class بسيط يحتوي على `id` و`title` و`body`. :::note -نحن نوسع [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة -كائنات `Posts`. بدون هذا، سنحتاج إلى تغيير الصنف يدويًا لتجاوز التساوي -(`equality`) ورمز التجزئة (`hashCode`) حتى نتمكن من التمييز بين كائني `Posts` -مختلفين. راجع [الحزمة](https://pub.dev/packages/equatable) لمزيد من التفاصيل. +نحن نرث من [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة +كائنات `Posts`. بدون ذلك سنحتاج لتعديل الـ class يدويًا وعمل override لـ +`equality` و`hashCode` حتى نميّز بين كائنين مختلفين من `Posts`. راجع +[الحزمة](https://pub.dev/packages/equatable) لمزيد من التفاصيل. ::: -الآن بعد أن أصبح لدينا نموذج كائن `Post`، دعنا نبدأ العمل على مكون منطق الأعمال -(Business Logic Component) أو (bloc). +الآن بعد أن أصبح لدينا model كائن `Post`، لنبدأ العمل على مكوّن منطق الأعمال +(Business Logic Component) أي `bloc`. ## أحداث المنشور (Post Events) -قبل أن نتعمق في التنفيذ، نحتاج إلى تحديد ما سيفعله `PostBloc` الخاص بنا. +قبل التعمّق في التنفيذ، نحتاج إلى تحديد ما الذي سيفعله `PostBloc`. -على مستوى عالٍ، سيستجيب لإدخال المستخدم (التمرير) ويجلب المزيد من المنشورات لكي -تعرضها طبقة العرض. لنبدأ بإنشاء `Event` الخاص بنا. +على مستوى عام، سيستجيب لإدخال المستخدم (التمرير) ويجلب المزيد من المنشورات حتى +تتمكن طبقة العرض من عرضها. لنبدأ بإنشاء `Event`. -سيستجيب `PostBloc` الخاص بنا لحدث واحد فقط؛ وهو `PostFetched` الذي ستتم إضافته -بواسطة طبقة العرض كلما احتاجت إلى المزيد من المنشورات لعرضها. نظرًا لأن حدث -`PostFetched` هو نوع من `PostEvent`، يمكننا إنشاء `bloc/post_event.dart` وتنفيذ -الحدث على النحو التالي. +سيستجيب `PostBloc` لحدث واحد فقط هو `PostFetched`، والذي تضيفه طبقة العرض كلما +احتاجت إلى مزيد من المنشورات. وبما أن `PostFetched` نوع من `PostEvent`، يمكننا +إنشاء `bloc/post_event.dart` وتنفيذه كالتالي. -للتلخيص، سيتلقى `PostBloc` الخاص بنا `PostEvents` ويحولها إلى `PostStates`. لقد -قمنا بتعريف جميع `PostEvents` (PostFetched)، لذا دعنا نحدد `PostState` الخاص بنا -بعد ذلك. +باختصار، سيتلقى `PostBloc` أحداث `PostEvents` ويحوّلها إلى حالات `PostStates`. +بعد تعريف `PostEvents` (`PostFetched`)، ننتقل لتعريف `PostState`. ## حالات المنشور (Post States) -ستحتاج طبقة العرض لدينا إلى عدة أجزاء من المعلومات لترتيب نفسها بشكل صحيح: +تحتاج طبقة العرض إلى عدة معلومات لتتمكن من البناء بشكل صحيح: - `PostInitial`: ستخبر طبقة العرض بأنها بحاجة إلى عرض مؤشر تحميل أثناء تحميل الدفعة الأولية من المنشورات. -- `PostSuccess`: ستخبر طبقة العرض بأن لديها محتوى لعرضه. +- `PostSuccess`: تخبر طبقة العرض أن لديها محتوى جاهزًا للعرض. - `posts`: ستكون قائمة (`List`) سيتم عرضها. - `hasReachedMax`: ستخبر طبقة العرض بما إذا كانت قد وصلت إلى الحد الأقصى لعدد المنشورات أم لا. - `PostFailure`: ستخبر طبقة العرض بحدوث خطأ أثناء جلب المنشورات. -يمكننا الآن إنشاء `bloc/post_state.dart` وتنفيذه على النحو التالي. +يمكننا الآن إنشاء `bloc/post_state.dart` وتنفيذه بالشكل التالي. :::note -من إعلان الصنف (class declaration) فقط، يمكننا أن نعرف أن `PostBloc` الخاص بنا -سيأخذ `PostEvents` كمدخلات ويخرج `PostStates`. +من class declaration فقط، يمكننا معرفة أن `PostBloc` يستقبل `PostEvents` كمدخلات +ويخرج `PostStates`. ::: -بعد ذلك، نحتاج إلى تسجيل معالج حدث للتعامل مع أحداث `PostFetched` الواردة. -استجابةً لحدث `PostFetched`، سنستدعي `_fetchPosts` لجلب المنشورات من واجهة برمجة -التطبيقات. +بعد ذلك، نحتاج لتسجيل event handler لمعالجة أحداث `PostFetched` الواردة. +استجابةً لهذا الحدث، سنستدعي `_fetchPosts` لجلب المنشورات من API. -سيقوم `PostBloc` الخاص بنا بإصدار (`emit`) حالات جديدة عبر `Emitter` -المقدم في معالج الحدث. تحقق من -[المفاهيم الأساسية](/ar/bloc-concepts/#التدفقات-streams) لمزيد من المعلومات. +سيقوم `PostBloc` بإصدار (`emit`) حالات جديدة عبر `Emitter` الممرر إلى +event handler. راجع [المفاهيم الأساسية](/ar/bloc-concepts/#التدفقات-streams) +لمزيد من المعلومات. -الآن في كل مرة تتم فيها إضافة `PostEvent`، إذا كان حدث `PostFetched` وهناك -المزيد من المنشورات لجلبها، فسيقوم `PostBloc` الخاص بنا بجلب الـ 20 منشورًا -التالية. +الآن، في كل مرة يُضاف فيها `PostEvent` وكان الحدث هو `PostFetched` وما زالت هناك +منشورات متاحة، سيقوم `PostBloc` بجلب 20 منشورًا إضافيًا. -ستُرجع واجهة برمجة التطبيقات مصفوفة فارغة إذا حاولنا الجلب بعد الحد الأقصى لعدد -المنشورات (100)، لذلك إذا حصلنا على مصفوفة فارغة، فسيقوم الـ bloc الخاص بنا -بإصدار (`emit`) الحالة الحالية باستثناء أننا سنقوم بتعيين `hasReachedMax` إلى -`true`. +ستعيد API مصفوفة فارغة إذا حاولنا الجلب بعد الحد الأقصى (100 منشور). لذلك إذا +حصلنا على مصفوفة فارغة، فسيقوم bloc بإصدار (`emit`) الحالة الحالية مع تعيين +`hasReachedMax` إلى `true`. إذا لم نتمكن من استرداد المنشورات، فإننا نصدر `PostStatus.failure`. إذا تمكنا من استرداد المنشورات، فإننا نصدر `PostStatus.success` والقائمة الكاملة للمنشورات. -أحد التحسينات التي يمكننا إجراؤها هو **تخنيق** (`throttle`) حدث `PostFetched` -لمنع إرسال طلبات غير ضرورية إلى واجهة برمجة التطبيقات الخاصة بنا. يمكننا القيام -بذلك باستخدام معلمة `transform` عندما نسجل معالج الحدث `_onFetched`. +أحد التحسينات الممكنة هو عمل **throttle** لحدث `PostFetched` لتجنب إرسال طلبات +غير ضرورية إلى API. ويمكن تنفيذ ذلك باستخدام معامل `transform` عند تسجيل معالج +الحدث `_onFetched`. :::note @@ -237,7 +231,7 @@ jsonplaceholder هي واجهة برمجة تطبيقات REST عبر الإنت تأكد من استيراد [`package:stream_transform`](https://pub.dev/packages/stream_transform) لاستخدام -واجهة برمجة تطبيقات `throttle`. +API الخاصة بـ `throttle`. ::: @@ -250,13 +244,12 @@ jsonplaceholder هي واجهة برمجة تطبيقات REST عبر الإنت title="lib/posts/bloc/post_bloc.dart" /> -عظيم! الآن بعد أن انتهينا من تنفيذ منطق الأعمال، كل ما تبقى هو تنفيذ طبقة العرض. +ممتاز! بعد الانتهاء من منطق الأعمال، المتبقي هو تنفيذ طبقة العرض. ## طبقة العرض (Presentation Layer) -في `main.dart`، يمكننا أن نبدأ بتنفيذ الدالة الرئيسية واستدعاء `runApp` لعرض -الويدجت الجذر لدينا. هنا، يمكننا أيضًا تضمين مراقب الـ bloc الخاص بنا -(`bloc observer`) لتسجيل الانتقالات وأي أخطاء. +في `main.dart`، نبدأ بتنفيذ الدالة الرئيسية واستدعاء `runApp` لعرض Widget الجذر. +وهنا يمكننا أيضًا تضمين `bloc observer` لتسجيل الانتقالات وأي أخطاء. -في ويدجت `PostsPage`، نستخدم `BlocProvider` لإنشاء وتوفير مثيل من `PostBloc` -للشجرة الفرعية. أيضًا، نضيف حدث `PostFetched` بحيث عندما يتم تحميل التطبيق، فإنه -يطلب الدفعة الأولية من المنشورات. +في Widget `PostsPage`، نستخدم `BlocProvider` لإنشاء وتوفير instance من +`PostBloc` للشجرة الفرعية. كما نضيف حدث `PostFetched` بحيث يطلب التطبيق الدفعة +الأولية من المنشورات عند التحميل. -بعد ذلك، نحتاج إلى تنفيذ عرض `PostsList` الخاص بنا والذي سيعرض منشوراتنا ويتصل -بـ `PostBloc` الخاص بنا. +بعد ذلك، نحتاج إلى تنفيذ `PostsList` التي ستعرض المنشورات وتتصل بـ `PostBloc`. ()`. +`PostsList` هي `StatefulWidget` لأنها تحتاج للاحتفاظ بـ `ScrollController`. في +`initState`، نضيف listener إلى `ScrollController` حتى نستجيب لأحداث التمرير. كما +نصل إلى instance من `PostBloc` عبر `context.read()`. ::: -بالانتقال إلى الأمام، تُرجع طريقة البناء (`build method`) لدينا `BlocBuilder`. -`BlocBuilder` هي ويدجت من Flutter من -[حزمة flutter_bloc](https://pub.dev/packages/flutter_bloc) تتولى بناء ويدجت -استجابةً لحالات الـ bloc الجديدة. في أي وقت تتغير فيه حالة `PostBloc` الخاصة -بنا، سيتم استدعاء دالة البناء لدينا بالحالة الجديدة `PostState`. +في خطوة البناء، تعيد `build method` لدينا `BlocBuilder`. و`BlocBuilder` هي +Widget من Flutter ضمن [حزمة flutter_bloc](https://pub.dev/packages/flutter_bloc) +تتولى بناء ويدجت استجابةً لحالات bloc الجديدة. وعند تغيّر حالة `PostBloc`، سيتم +استدعاء `builder` بالحالة الجديدة `PostState`. :::caution -نحتاج إلى تذكر تنظيف ما قمنا به والتخلص من `ScrollController` الخاص بنا عند -التخلص من `StatefulWidget`. +يجب أن نتذكر تنظيف الموارد والتخلّص من `ScrollController` عند التخلص من +`StatefulWidget`. ::: -عندما يقوم المستخدم بالتمرير، نحسب المسافة التي مررها لأسفل الصفحة، وإذا كانت -المسافة لدينا ≥ 90% من `maxScrollextent`، فإننا نضيف حدث `PostFetched` من أجل -تحميل المزيد من المنشورات. +عند تمرير المستخدم، نحسب المسافة التي وصل إليها داخل الصفحة. وإذا بلغت المسافة +`>= 90%` من `maxScrollExtent` نضيف حدث `PostFetched` لتحميل مزيد من المنشورات. -بعد ذلك، نحتاج إلى تنفيذ ويدجت `BottomLoader` الخاص بنا والذي سيشير إلى المستخدم -بأننا نقوم بتحميل المزيد من المنشورات. +بعد ذلك، نحتاج لتنفيذ Widget `BottomLoader` التي توضح للمستخدم أننا نحمّل المزيد +من المنشورات. -أخيرًا، نحتاج إلى تنفيذ `PostListItem` الخاص بنا والذي سيعرض `Post` فرديًا. +أخيرًا، نحتاج إلى تنفيذ `PostListItem` التي تعرض عنصر `Post` واحدًا. -في هذه المرحلة، يجب أن نكون قادرين على تشغيل تطبيقنا ويجب أن يعمل كل شيء؛ ومع -ذلك، هناك شيء آخر يمكننا القيام به. +في هذه المرحلة يفترض أن يعمل التطبيق بشكل صحيح، لكن ما زال هناك تحسين إضافي. -إحدى الميزات الإضافية لاستخدام مكتبة bloc هي أنه يمكننا الوصول إلى جميع -**الانتقالات** (`Transitions`) في مكان واحد. +من مزايا مكتبة bloc أننا نستطيع الوصول إلى جميع الانتقالات (`Transitions`) في +مكان واحد. -يُطلق على التغيير من حالة إلى أخرى اسم **انتقال** (`Transition`). +التغيير من حالة إلى أخرى يسمى انتقالًا (`Transition`). :::note -يتكون **الانتقال** من الحالة الحالية، والحدث، والحالة التالية. +يتكون الانتقال (`Transition`) من الحالة الحالية، والحدث، والحالة التالية. ::: @@ -347,7 +335,7 @@ jsonplaceholder هي واجهة برمجة تطبيقات REST عبر الإنت التطبيقات الأكبر أن يكون لدينا العديد من الكتل التي تدير أجزاء مختلفة من حالة التطبيق. -إذا أردنا أن نكون قادرين على فعل شيء ما استجابةً لجميع **الانتقالات**، يمكننا +إذا أردنا تنفيذ منطق معيّن استجابةً لكل الانتقالات (`Transitions`)، يمكننا ببساطة إنشاء `BlocObserver` خاص بنا. -بعد ذلك، يمكننا تثبيت جميع الاعتمادات الخاصة بنا +بعد ذلك، يمكننا تثبيت جميع التبعيات (dependencies). @@ -48,7 +47,7 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as سنبدأ بإنشاء مجلد `packages/authentication_repository` في جذر المشروع والذي سيحتوي على جميع الحزم الداخلية. -على مستوى الهيكلية العامة، يجب أن يبدو هيكل الدليل كما يلي: +على مستوى عالٍ، يجب أن يبدو هيكل المجلدات بالشكل التالي: ``` ├── android @@ -81,16 +80,17 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as title="packages/authentication_repository/lib/src/authentication_repository.dart" /> -توفر `AuthenticationRepository` تدفق `Stream` من تحديثات `AuthenticationStatus` -والذي سيتم استخدامه لإبلاغ التطبيق عندما يقوم المستخدم بتسجيل الدخول أو الخروج. +توفّر `AuthenticationRepository` تدفق `Stream` من تحديثات +`AuthenticationStatus`، ويُستخدم هذا التدفق لإبلاغ التطبيق عند تسجيل المستخدم +الدخول أو الخروج. -بالإضافة إلى ذلك، هناك طرق `logIn` و `logOut` مبسطة للشرح، لكنها يمكن بسهولة +بالإضافة إلى ذلك، هناك دالتا `logIn` و`logOut` مبسّطتان للشرح، لكن يمكن بسهولة توسيعها للمصادقة باستخدام `FirebaseAuth` مثلاً أو أي مزود مصادقة آخر. :::note -بما أننا ندير `StreamController` داخليًا، يتم عرض طريقة `dispose` ليتمكن -المستخدم من إغلاق الـ controller عندما لا يكون مطلوبًا بعد الآن. +بما أننا ندير `StreamController` داخليًا، تم توفير دالة `dispose` لإغلاق +controller عندما لا يعود مطلوبًا. ::: @@ -128,8 +128,8 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as title="packages/user_repository/pubspec.yaml" /> -حزمة `user_repository` ستكون مسؤولة عن نطاق المستخدم وستوفر واجهات برمجية (APIs) -للتفاعل مع المستخدم الحالي. +حزمة `user_repository` مسؤولة عن نطاق المستخدم، وتوفّر APIs للتفاعل مع المستخدم +الحالي. أول شيء سنحدده هو نموذج المستخدم في الملف `packages/user_repository/lib/src/models/user.dart`: @@ -167,9 +167,8 @@ import FlutterPubGetSnippet from '~/components/tutorials/FlutterPubGetSnippet.as /> في هذا المثال البسيط، توفّر `UserRepository` دالة واحدة فقط هي `getUser` والتي -تسترجع المستخدم الحالي. نحن هنا نقوم بعمل تمثيل تجريبي (stubbing)، لكن في -التطبيق الفعلي ستكون هذه الدالة هي التي تستعلم المستخدم الحالي من الخادم -(backend). +تسترجع المستخدم الحالي. نحن هنا نستخدم stubbing، لكن في التطبيق الفعلي ستكون هذه +الدالة هي التي تستعلم المستخدم الحالي من الخادم (backend). لقد اقتربنا من الانتهاء من حزمة `user_repository`، والشيء الوحيد المتبقي هو إنشاء ملف `user_repository.dart` في المسار `packages/user_repository/lib` والذي @@ -291,8 +290,8 @@ blocs تلقائيًا. `emit.onEach` يقوم بإنشاء اشتراك داخلي في التدفق ويتولى إلغاءه تلقائيًا عند إغلاق `AuthenticationBloc` أو تدفق `status`. -إذا أصدر تدفق `status` خطأً، فإن `addError` يمرر الخطأ مع `stackTrace` لأي -`BlocObserver` مستمع. +إذا أصدر تدفق `status` خطأً، فإن `addError` يمرّر الخطأ مع `stackTrace` لأي +`BlocObserver` يستمع. :::caution @@ -334,22 +333,21 @@ blocs تلقائيًا. :::note -تم تقسيم `app.dart` إلى جزأين: `App` و `AppView`. يتحمل `App` مسؤولية -إنشاء/توفير `AuthenticationBloc` الذي سيتم استهلاكه من قبل `AppView`. هذا -الانفصال/الغير مترابط يسمح لنا باختبار كل من ويدجت `App` و`AppView` بسهولة -لاحقًا. +تم تقسيم `app.dart` إلى جزأين: `App` و`AppView`. يتحمل `App` مسؤولية إنشاء/توفير +`AuthenticationBloc` الذي سيتم استهلاكه من قبل `AppView`. هذا الفصل (decoupling) +يسمح لنا باختبار كل من Widget `App` و`AppView` بسهولة لاحقًا. ::: :::note -يُستخدم `RepositoryProvider` لتوفير نسخة واحدة من `AuthenticationRepository` +يُستخدم `RepositoryProvider` لتوفير instance واحدة من `AuthenticationRepository` لكامل التطبيق، وهو ما سيكون مفيدًا لاحقًا. ::: -افتراضيًا، `BlocProvider` يكون كسولًا (lazy) ولا يستدعي `create` إلا عند أول -وصول إلى الـ Bloc. بما أن `AuthenticationBloc` يجب أن يشترك دائمًا في تيار +افتراضيًا، `BlocProvider` يكون lazy ولا يستدعي `create` إلا عند أول وصول إلى الـ +Bloc. وبما أن `AuthenticationBloc` يجب أن يشترك دائمًا في stream `AuthenticationStatus` فورًا (عبر الحدث `AuthenticationSubscriptionRequested`)، يمكننا تجاوز هذا السلوك صراحةً عن طريق ضبط `lazy: false`. @@ -378,7 +376,7 @@ lib :::tip -`SplashPage` تعرض مسار (`Route`) ثابت مما يسهل التنقل إليها باستخدام +`SplashPage` توفّر مسارًا (`Route`) ثابتًا، مما يجعل التنقل إليها سهلًا باستخدام `Navigator.of(context).push(SplashPage.route())`; ::: @@ -440,18 +438,18 @@ lib title="lib/login/models/models.dart" /> -### الـ Login Bloc +### Login Bloc -يقوم الـ `LoginBloc` بإدارة حالة `LoginForm` ويتولى التحقق من صحة إدخالات اسم +يقوم `LoginBloc` بإدارة حالة `LoginForm` ويتولى التحقق من صحة إدخالات اسم المستخدم وكلمة المرور بالإضافة إلى حالة النموذج. #### login_event.dart في هذا التطبيق، هناك ثلاثة أنواع مختلفة من `LoginEvent`: -- `LoginUsernameChanged`: يُخطر الـ bloc بأنه تم تعديل اسم المستخدم. -- `LoginPasswordChanged`: يُخطر الـ bloc بأنه تم تعديل كلمة المرور. -- `LoginSubmitted`: يُخطر الـ bloc بأنه تم تقديم النموذج. +- `LoginUsernameChanged`: يخطر الـ bloc بأنه تم تعديل اسم المستخدم. +- `LoginPasswordChanged`: يخطر الـ bloc بأنه تم تعديل كلمة المرور. +- `LoginSubmitted`: يخطر الـ bloc بأنه تم تقديم النموذج. -يعتمد الـ `LoginBloc` على `AuthenticationRepository` لأنه عند تقديم النموذج، -يقوم باستدعاء `logIn`. الحالة الابتدائية للـ bloc هي `pure`، مما يعني أن -الإدخالات والنموذج لم يتم لمسهم أو التفاعل معهم بعد. +يعتمد `LoginBloc` على `AuthenticationRepository` لأنه عند تقديم النموذج يستدعي +`logIn`. الحالة الابتدائية للـ bloc هي `pure`، ما يعني أن الحقول والنموذج لم يتم +التفاعل معهما بعد. -عندما يتغير اسم المستخدم أو كلمة المرور، يقوم الـ bloc بإنشاء نسخة “متسخة” +عندما يتغير اسم المستخدم أو كلمة المرور، يقوم الـ bloc بإنشاء نسخة "متسخة" (dirty) من نموذج `Username` أو `Password` ويُحدّث حالة النموذج عبر واجهة برمجة التطبيقات `Formz.validate`. @@ -500,7 +498,7 @@ bloc باستدعاء `logIn` ويُحدّث الحالة بناءً على نت ### صفحة تسجيل الدخول -تتولى `LoginPage` مسؤولية توفير الـ `Route` بالإضافة إلى إنشاء وتوفير الـ +تتولى `LoginPage` مسؤولية توفير الـ `Route` بالإضافة إلى إنشاء وتوفير `LoginBloc` لـ `LoginForm`. ()` للبحث عن نسخة من +يُستخدم `context.read()` للعثور على instance من `AuthenticationRepository` عبر `BuildContext`. ::: ### نموذج تسجيل الدخول -يتولى `LoginForm` إخطار الـ `LoginBloc` بأحداث المستخدم ويستجيب أيضًا للتغيرات -في الحالة باستخدام `BlocBuilder` و `BlocListener`. +يتولى `LoginForm` إخطار `LoginBloc` بأحداث المستخدم ويستجيب أيضًا للتغيرات في +الحالة باستخدام `BlocBuilder` و `BlocListener`. bloc.state.user.id)` وعرضه باستخدام -ويدجت `Text`. بالإضافة إلى ذلك، عند الضغط على زر تسجيل الخروج يتم إضافة حدث +Widget `Text`. بالإضافة إلى ذلك، عند الضغط على زر تسجيل الخروج يتم إضافة حدث `AuthenticationLogoutPressed` إلى الـ `AuthenticationBloc`. -يمكننا بعد ذلك استبدال محتويات `pubspec.yaml` بما يلي: +بعد ذلك، يمكننا استبدال محتويات `pubspec.yaml` بما يلي: -كل ما يفعله صنف `Ticker` الخاص بنا هو كشف دالة `tick` تأخذ عدد النبضات (الثواني) -التي نريدها وتُرجع دفقًا (`stream`) يُصدر الثواني المتبقية كل ثانية. +كل ما يفعله class `Ticker` هو توفير الدالة `tick` التي تستقبل عدد النبضات +(الثواني) المطلوب، ثم ترجع stream يصدر الثواني المتبقية كل ثانية. -بعد ذلك، نحتاج إلى إنشاء `TimerBloc` الخاص بنا والذي سيستهلك `Ticker`. +بعد ذلك، نحتاج إلى إنشاء `TimerBloc` الذي سيستهلك `Ticker`. -## كتلة المؤقت (Timer Bloc) +## Timer Bloc ### حالة المؤقت (TimerState) -سنبدأ بتعريف `TimerStates` التي يمكن أن يكون عليها `TimerBloc` الخاص بنا. +سنبدأ بتعريف `TimerStates` التي يمكن أن تكون عليها `TimerBloc`. يمكن أن تكون حالة `TimerBloc` الخاصة بنا واحدة مما يلي: @@ -109,8 +109,8 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn - `TimerRunPause`: متوقف مؤقتًا عند مدة متبقية معينة. - `TimerRunComplete`: اكتمل بمدة متبقية 0. -كل من هذه الحالات سيكون له تأثير على واجهة المستخدم والإجراءات التي يمكن -للمستخدم القيام بها. على سبيل المثال: +كل حالة من هذه الحالات تؤثر على واجهة المستخدم والإجراءات المتاحة للمستخدم. على +سبيل المثال: - إذا كانت الحالة هي `TimerInitial`، فسيتمكن المستخدم من بدء المؤقت. - إذا كانت الحالة هي `TimerRunInProgress`، فسيتمكن المستخدم من إيقاف المؤقت @@ -119,7 +119,7 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn تعيينه. - إذا كانت الحالة هي `TimerRunComplete`، فسيتمكن المستخدم من إعادة تعيين المؤقت. -للحفاظ على جميع ملفات الـ bloc الخاصة بنا معًا، دعنا ننشئ دليل `bloc` مع +للحفاظ على جميع ملفات bloc معًا، لننشئ مجلد `bloc` ونضيف `bloc/timer_state.dart`. :::tip @@ -127,7 +127,7 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn يمكنك استخدام إضافات [IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) أو [VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc) -لإنشاء ملفات الـ bloc التالية تلقائيًا لك. +لإنشاء ملفات bloc التالية تلقائيًا. ::: @@ -136,17 +136,16 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn title="lib/timer/bloc/timer_state.dart" /> -لاحظ أن جميع `TimerStates` توسع الصنف الأساسي المجرد `TimerState` الذي يحتوي على -خاصية المدة (`duration`). هذا لأنه بغض النظر عن الحالة التي يوجد بها `TimerBloc` -الخاص بنا، نريد أن نعرف مقدار الوقت المتبقي. بالإضافة إلى ذلك، يوسع `TimerState` -من `Equatable` لتحسين الكود الخاص بنا من خلال ضمان أن تطبيقنا لا يؤدي إلى إعادة -بناء إذا حدثت نفس الحالة. +لاحظ أن جميع `TimerStates` ترث من abstract base class `TimerState` الذي يحتوي +على الخاصية `duration`. السبب هو أننا نريد معرفة الوقت المتبقي مهما كانت حالة +`TimerBloc`. بالإضافة إلى ذلك، يرث `TimerState` من `Equatable` لتحسين الأكواد +البرمجية ومنع إعادة البناء عندما تتكرر الحالة نفسها. -بعد ذلك، دعنا نحدد وننفذ `TimerEvents` التي سيعالجها `TimerBloc` الخاص بنا. +بعد ذلك، لنحدّد وننفّذ `TimerEvents` التي سيعالجها `TimerBloc`. ### حدث المؤقت (TimerEvent) -سيحتاج `TimerBloc` الخاص بنا إلى معرفة كيفية معالجة الأحداث التالية: +يحتاج `TimerBloc` إلى معرفة كيفية معالجة الأحداث التالية: - `TimerStarted`: يُعلم `TimerBloc` بضرورة بدء المؤقت. - `TimerPaused`: يُعلم `TimerBloc` بضرورة إيقاف المؤقت مؤقتًا. @@ -158,71 +157,67 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn إذا لم تستخدم إضافات [IntelliJ](https://plugins.jetbrains.com/plugin/12129-bloc-code-generator) أو [VSCode](https://marketplace.visualstudio.com/items?itemName=FelixAngelov.bloc)، -فقم بإنشاء `bloc/timer_event.dart` ودعنا ننفذ تلك الأحداث. +فأنشئ `bloc/timer_event.dart` ونفّذ هذه الأحداث. -بعد ذلك، دعنا ننفذ `TimerBloc`! +والآن لننفّذ `TimerBloc`. ### TimerBloc -إذا لم تكن قد قمت بذلك بالفعل، فقم بإنشاء `bloc/timer_bloc.dart` وأنشئ -`TimerBloc` فارغًا. +إذا لم تكن قد فعلت ذلك بعد، فأنشئ `bloc/timer_bloc.dart` وأنشئ `TimerBloc` +فارغًا. -أول شيء نحتاج إلى القيام به هو تحديد الحالة الأولية لـ `TimerBloc` الخاص بنا. في -هذه الحالة، نريد أن يبدأ `TimerBloc` في حالة `TimerInitial` بمدة محددة مسبقًا -تبلغ دقيقة واحدة (60 ثانية). +أول خطوة هي تحديد الحالة الأولية لـ `TimerBloc`. هنا نريد أن يبدأ `TimerBloc` في +حالة `TimerInitial` بمدة مضبوطة مسبقًا تبلغ دقيقة واحدة (60 ثانية). -بعد ذلك، نحتاج إلى تحديد التبعية على `Ticker` الخاص بنا. +بعد ذلك، نحتاج إلى تحديد dependency على `Ticker`. -نقوم أيضًا بتعريف `StreamSubscription` لـ `Ticker` الخاص بنا والذي سنتطرق إليه -بعد قليل. +كما نعرّف `StreamSubscription` لـ `Ticker` وسنعود إليها بعد قليل. -في هذه المرحلة، كل ما تبقى هو تنفيذ معالجات الأحداث. لتحسين قابلية القراءة، أحب -تقسيم كل معالج حدث إلى دالة مساعدة خاصة به. سنبدأ بحدث `TimerStarted`. +في هذه المرحلة، المتبقي هو تنفيذ event handlers. ولتحسين قابلية القراءة، نفصل كل +معالج في helper function مستقلة. سنبدأ بحدث `TimerStarted`. -إذا تلقى `TimerBloc` حدث `TimerStarted`، فإنه يدفع حالة `TimerRunInProgress` -بمدة البدء. بالإضافة إلى ذلك، إذا كان هناك بالفعل `_tickerSubscription` مفتوح، -فنحن بحاجة إلى إلغائه لتحرير الذاكرة. نحتاج أيضًا إلى تجاوز طريقة `close` في -`TimerBloc` الخاص بنا حتى نتمكن من إلغاء `_tickerSubscription` عند إغلاق -`TimerBloc`. أخيرًا، نستمع إلى دفق `_ticker.tick` وفي كل نبضة، نضيف حدث -`_TimerTicked` بالمدة المتبقية. +إذا استقبل `TimerBloc` حدث `TimerStarted`، فإنه يصدر حالة `TimerRunInProgress` +بمدة البداية. وإذا كانت `_tickerSubscription` مفتوحة مسبقًا، فنحتاج إلى إلغائها +لتحرير الذاكرة. كما نحتاج إلى عمل override للدالة `close` في `TimerBloc` حتى +نلغي `_tickerSubscription` عند إغلاق الـ bloc. أخيرًا، نستمع إلى stream +`_ticker.tick`، ومع كل نبضة نضيف حدث `_TimerTicked` بالمدة المتبقية. بعد ذلك، دعنا ننفذ معالج حدث `_TimerTicked`. -في كل مرة يتم فيها تلقي حدث `_TimerTicked`، إذا كانت مدة النبضة أكبر من 0، فنحن -بحاجة إلى دفع حالة `TimerRunInProgress` محدثة بالمدة الجديدة. بخلاف ذلك، إذا -كانت مدة النبضة هي 0، فقد انتهى المؤقت الخاص بنا ونحتاج إلى دفع حالة -`TimerRunComplete`. +في كل مرة نستقبل فيها حدث `_TimerTicked`، إذا كانت مدة النبضة أكبر من 0 فنحن +بحاجة إلى إصدار حالة `TimerRunInProgress` محدثة بالمدة الجديدة. أما إذا كانت مدة +النبضة تساوي 0 فقد انتهى المؤقّت ونحتاج إلى إصدار حالة `TimerRunComplete`. الآن دعنا ننفذ معالج حدث `TimerPaused`. -في `_onPaused` إذا كانت `state` الخاصة بـ `TimerBloc` هي `TimerRunInProgress`، -فيمكننا إيقاف `_tickerSubscription` مؤقتًا ودفع حالة `TimerRunPause` بالمدة -الحالية للمؤقت. +في `_onPaused`، إذا كانت `state` الخاصة بـ `TimerBloc` هي `TimerRunInProgress` +فيمكننا إيقاف `_tickerSubscription` مؤقتًا وإصدار حالة `TimerRunPause` بالمدة +الحالية. -بعد ذلك، دعنا ننفذ معالج حدث `TimerResumed` حتى نتمكن من استئناف المؤقت. +بعد ذلك، لننفذ معالج حدث `TimerResumed` حتى نستأنف المؤقّت. -معالج حدث `TimerResumed` مشابه جدًا لمعالج حدث `TimerPaused`. إذا كان -`TimerBloc` في `state` من نوع `TimerRunPause` وتلقى حدث `TimerResumed`، فإنه -يستأنف `_tickerSubscription` ويدفع حالة `TimerRunInProgress` بالمدة الحالية. +معالج حدث `TimerResumed` مشابه جدًا لمعالج حدث `TimerPaused`. إذا كانت حالة +`TimerBloc` من نوع `TimerRunPause` ووصل حدث `TimerResumed`، فإنه يستأنف +`_tickerSubscription` ويصدر حالة `TimerRunInProgress` بالمدة الحالية. أخيرًا، نحتاج إلى تنفيذ معالج حدث `TimerReset`. @@ -231,49 +226,48 @@ import BackgroundSnippet from '~/components/tutorials/flutter-timer/BackgroundSn title="lib/timer/bloc/timer_bloc.dart" /> -إذا تلقى `TimerBloc` حدث `TimerReset`، فإنه يحتاج إلى إلغاء -`_tickerSubscription` الحالي حتى لا يتم إخطاره بأي نبضات إضافية ويدفع حالة +إذا استقبل `TimerBloc` حدث `TimerReset`، فإنه يحتاج إلى إلغاء +`_tickerSubscription` الحالية حتى لا يتلقى أي نبضات إضافية، ثم يصدر حالة `TimerInitial` بالمدة الأصلية. -هذا كل ما يتعلق بـ `TimerBloc`. الآن كل ما تبقى هو تنفيذ واجهة المستخدم لتطبيق -المؤقت الخاص بنا. +هذا كل ما يخص `TimerBloc`. والمتبقي الآن هو تنفيذ واجهة المستخدم (UI) للتطبيق. ## واجهة مستخدم التطبيق (Application UI) ### MyApp -يمكننا أن نبدأ بحذف محتويات `main.dart` واستبدالها بما يلي. +يمكننا البدء بحذف محتويات `main.dart` واستبدالها بما يلي. -بعد ذلك، دعنا ننشئ ويدجت 'App' الخاص بنا في `app.dart`، والذي سيكون جذر تطبيقنا. +بعد ذلك، لننشئ Widget التطبيق في `app.dart`، والتي ستكون جذر التطبيق. -بعد ذلك، نحتاج إلى تنفيذ ويدجت `Timer` الخاص بنا. +بعد ذلك، نحتاج إلى تنفيذ Widget `Timer`. ### المؤقت (Timer) -سيكون ويدجت `Timer` الخاص بنا (`lib/timer/view/timer_page.dart`) مسؤولاً عن عرض -الوقت المتبقي جنبًا إلى جنب مع الأزرار المناسبة التي ستمكن المستخدمين من بدء -المؤقت وإيقافه مؤقتًا وإعادة تعيينه. +Widget `Timer` في (`lib/timer/view/timer_page.dart`) مسؤولة عن عرض الوقت المتبقي +مع الأزرار المناسبة التي تمكّن المستخدم من بدء المؤقّت وإيقافه مؤقتًا وإعادة +تعيينه. -حتى الآن، نحن نستخدم `BlocProvider` فقط للوصول إلى مثيل `TimerBloc` الخاص بنا. +حتى الآن، نستخدم `BlocProvider` فقط للوصول إلى instance من `TimerBloc`. -بعد ذلك، سنقوم بتنفيذ ويدجت `Actions` الخاص بنا والذي سيحتوي على الإجراءات -المناسبة (بدء، إيقاف مؤقت، وإعادة تعيين). +بعد ذلك، سننفّذ Widget `Actions` والتي ستحتوي على الإجراءات المناسبة (بدء، إيقاف +مؤقت، وإعادة تعيين). ### ملف التجميع (Barrel) -لتنظيف عمليات الاستيراد الخاصة بنا من قسم `Timer`، نحتاج إلى إنشاء ملف تجميع +لتنظيم عمليات الاستيراد من قسم `Timer`، نحتاج إلى إنشاء ملف تجميعي (`barrel file`) باسم `timer/timer.dart`. -ويدجت `Actions` هو مجرد `StatelessWidget` آخر يستخدم `BlocBuilder` لإعادة بناء -واجهة المستخدم في كل مرة نحصل فيها على `TimerState` جديدة. يستخدم `Actions` -الدالة `context.read()` للوصول إلى مثيل `TimerBloc` ويُرجع أزرار -`FloatingActionButton` مختلفة بناءً على الحالة الحالية لـ `TimerBloc`. يضيف كل -زر من أزرار `FloatingActionButton` حدثًا في دالة رد الاتصال `onPressed` لإخطار -`TimerBloc`. - -إذا كنت تريد تحكمًا دقيقًا في وقت استدعاء دالة `builder`، يمكنك توفير -`buildWhen` اختياريًا لـ `BlocBuilder`. يأخذ `buildWhen` حالة الـ bloc السابقة -وحالة الـ bloc الحالية ويُرجع قيمة منطقية (`boolean`). إذا أرجع `buildWhen` -القيمة `true`، فسيتم استدعاء `builder` بالحالة وسيتم إعادة بناء الويدجت. إذا -أرجع `buildWhen` القيمة `false`، فلن يتم استدعاء `builder` بالحالة ولن تحدث +Widget `Actions` هي `StatelessWidget` تستخدم `BlocBuilder` لإعادة بناء واجهة +المستخدم كلما حصلنا على `TimerState` جديدة. تستخدم `Actions` الدالة +`context.read()` للوصول إلى instance من `TimerBloc`، وتُرجع أزرار +`FloatingActionButton` مختلفة حسب الحالة الحالية لـ `TimerBloc`. كل زر من أزرار +`FloatingActionButton` يضيف event داخل callback `onPressed` لإخطار `TimerBloc`. + +إذا أردت تحكمًا أدق في توقيت استدعاء `builder`، يمكنك تمرير `buildWhen` +اختياريًا إلى `BlocBuilder`. تستقبل `buildWhen` الحالة السابقة والحالة الحالية +للـ bloc وتعيد قيمة منطقية (`boolean`). إذا أعادت `true` فسيُستدعى `builder` +بالحالة وتحدث إعادة البناء. وإذا أعادت `false` فلن يُستدعى `builder` ولن تحدث إعادة بناء. -في هذه الحالة، لا نريد إعادة بناء ويدجت `Actions` في كل نبضة لأن ذلك سيكون غير -فعال. بدلاً من ذلك، نريد فقط إعادة بناء `Actions` إذا تغير `runtimeType` لـ +في هذه الحالة، لا نريد إعادة بناء Widget `Actions` في كل نبضة لأن ذلك غير فعّال. +بدلًا من ذلك، نريد إعادة بناء `Actions` فقط إذا تغيّر `runtimeType` لـ `TimerState` (على سبيل المثال: TimerInitial => TimerRunInProgress، TimerRunInProgress => TimerRunPause، إلخ...). -نتيجة لذلك، إذا قمنا بتلوين الويدجت عشوائيًا في كل عملية إعادة بناء، فسيبدو +نتيجةً لذلك، إذا قمنا بتلوين الـ Widgets عشوائيًا عند كل إعادة بناء، فسيبدو الأمر كما يلي: ![BlocBuilder buildWhen demo](https://cdn-images-1.medium.com/max/1600/1*YyjpH1rcZlYWxCX308l_Ew.gif) :::note -على الرغم من أن ويدجت `Text` يتم إعادة بناؤه في كل نبضة، فإننا نعيد بناء -`Actions` فقط إذا كانت بحاجة إلى إعادة بناء. +على الرغم من أن Widget `Text` تُعاد بناؤها في كل نبضة، فإننا نعيد بناء `Actions` +فقط إذا كانت بحاجة إلى إعادة بناء. ::: ### الخلفية (Background) -أخيرًا، أضف ويدجت الخلفية كما يلي: +أخيرًا، أضف Widget الخلفية كما يلي: ### تجميع كل شيء معًا -هذا كل ما في الأمر! في هذه المرحلة، لدينا تطبيق مؤقت قوي إلى حد ما يعيد بناء -الويدجت التي تحتاج إلى إعادة بناء فقط بكفاءة. +هذا كل ما في الأمر! في هذه المرحلة أصبح لدينا تطبيق مؤقّت جيد يعيد بناء Widgets +التي تحتاج فقط إلى إعادة البناء بكفاءة. يمكن العثور على المصدر الكامل لهذا المثال [هنا](https://github.com/felangel/Bloc/tree/master/examples/flutter_timer). diff --git a/docs/src/content/docs/ar/tutorials/flutter-todos.mdx b/docs/src/content/docs/ar/tutorials/flutter-todos.mdx index 5fbda01ea11..3f1c1aa09b5 100644 --- a/docs/src/content/docs/ar/tutorials/flutter-todos.mdx +++ b/docs/src/content/docs/ar/tutorials/flutter-todos.mdx @@ -1,6 +1,7 @@ --- title: Flutter Todos -description: دليل متعمق لبناء تطبيق مهام (Todos) في فلاتر باستخدام مكتبة bloc. +description: + دليل متعمّق لبناء تطبيق مهام (Todos) في Flutter باستخدام مكتبة Bloc. sidebar: order: 6 --- @@ -18,8 +19,7 @@ import EditTodosPageTreeSnippet from '~/components/tutorials/flutter-todos/EditT ![advanced](https://img.shields.io/badge/level-advanced-red.svg) -في هذا الدليل التعليمي، سنقوم ببناء تطبيق مهام (Todos) باستخدام فلاتر مع مكتبة -Bloc. +في هذا الدليل التعليمي، سنبني تطبيق مهام (Todos) في Flutter باستخدام مكتبة Bloc. ![demo](~/assets/tutorials/flutter-todos.gif) @@ -30,18 +30,18 @@ Bloc. - [الهيكلية الطبقية](/ar/architecture) لفصل الاهتمامات/المسؤوليات وتسهيل إعادة الاستخدام. - [BlocObserver](/ar/bloc-concepts#blocobserver) لمراقبة تغييرات الحالة. -- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، ويدجيت في Flutter يوفر - Bloc للأبناء. -- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، ويدجيت في Flutter يتولى - بناء الويدجيت استجابةً للحالات الجديدة. -- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، ويدجيت في Flutter ينفذ - تأثيرات جانبية استجابةً لتغيرات الحالة. -- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، ويدجيت في - Flutter يوفر مستودع (repository) للأبناء. +- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter + توفر Bloc للأبناء. +- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter + تتولى إعادة البناء استجابةً للحالات الجديدة. +- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي Widget في Flutter + تنفذ تأثيرات جانبية استجابةً لتغيرات الحالة. +- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي Widget + في Flutter توفر مستودع (repository) للأبناء. - [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable) لمنع عمليات إعادة البناء غير الضرورية. -- [MultiBlocListener](/ar/flutter-bloc-concepts#multibloclistener)، ويدجيت في - Flutter يقلل التعشيش عند استخدام عدة BlocListeners. +- [MultiBlocListener](/ar/flutter-bloc-concepts#multibloclistener)، وهي Widget + في Flutter تقلل التعشيش عند استخدام عدة BlocListeners. ## الإعداد @@ -52,7 +52,7 @@ Bloc. :::note -قم بتثبيت `very_good_cli` باستخدام الأمر التالي +قم بتثبيت `very_good_cli` باستخدام الأمر التالي: @@ -70,7 +70,7 @@ Bloc. title="pubspec.yaml" /> -وأخيرًا، نقوم بتثبيت جميع التبعيّات: +وأخيرًا، ثبّت جميع التبعيات (dependencies): @@ -80,8 +80,8 @@ Bloc. -نقوم بتقسيم المشروع إلى عدة حزم للحفاظ على تبعيات واضحة وصريحة لكل حزمة مع حدود -واضحة تفرض +نقسم المشروع إلى عدة حزم للحفاظ على تبعيات واضحة وصريحة لكل حزمة، مع حدود واضحة +تفرض [مبدأ المسؤولية الواحدة](https://en.wikipedia.org/wiki/Single-responsibility_principle). تؤدي هذه الطريقة في تنظيم المشروع إلى فوائد عديدة تشمل، ولكن لا تقتصر على: @@ -107,7 +107,7 @@ Bloc. - طبقة المجال (Domain) - طبقة المميزات - طبقة العرض/UI (widgets) - - منطق العمل (blocs/cubits) + - منطق الأعمال (blocs/cubits) **طبقة البيانات** @@ -119,18 +119,18 @@ Bloc. **طبقة المجال** -تجمع هذه الطبقة بين مزود بيانات أو أكثر وتطبق "قواعد العمل/الأعمال" على -البيانات. كل مكون في هذه الطبقة يسمى repository وتدير كل repository مجالًا -واحدًا عادةً. الحزم في طبقة الـ repository ينبغي أن تتعامل فقط مع طبقة البيانات. -في هذا المثال، تتكون طبقة الـ repository لدينا من حزمة `todos_repository`. +تجمع هذه الطبقة بين مزود بيانات أو أكثر وتطبق قواعد الأعمال على البيانات. كل +مكون في هذه الطبقة يسمى repository وتدير كل repository مجالًا واحدًا عادةً. +الحزم في طبقة الـ repository ينبغي أن تتعامل فقط مع طبقة البيانات. في هذا +المثال، تتكون طبقة الـ repository لدينا من حزمة `todos_repository`. **طبقة المميزات** تحتوي هذه الطبقة على جميع الميزات وحالات الاستخدام الخاصة بالتطبيق. تتضمن كل -ميزة عادةً بعض عناصر الواجهة (UI) ومنطق العمل. يجب أن تكون الميزات مستقلة عن +ميزة عادةً بعض عناصر الواجهة (UI) ومنطق الأعمال. يجب أن تكون الميزات مستقلة عن بعضها البعض لتسهيل إضافتها أو إزالتها دون التأثير على بقية قاعدة الكود. ضمن كل -ميزة، يتم إدارة حالة الميزة ومنطق العمل بواسطة blocs. تتفاعل الـ blocs مع بدون -أو مع مستودعات (repositories) متعددة. تستجيب الـ blocs للأحداث (events) وتصدر +ميزة، يتم إدارة حالة الميزة ومنطق الأعمال بواسطة blocs. تتفاعل الـ blocs مع صفر +أو أكثر من المستودعات (repositories). تستجيب الـ blocs للأحداث (events) وتصدر الحالات (states) التي تؤدي إلى تغيير واجهة المستخدم. الأدوات (widgets) في كل ميزة تعتمد عادةً على الـ bloc المناظر وتقوم بعرض واجهة المستخدم بناءً على الحالة الحالية. يمكن لواجهة المستخدم إعلام الـ bloc بإدخالات المستخدم عبر الأحداث. في @@ -333,7 +333,7 @@ Bloc. ### التطبيق (App) -`App` يغلف ويدير الويجت `RepositoryProvider` الذي يوفر الـ repository لجميع +`App` يغلف ويدير Widget `RepositoryProvider` الذي يوفر الـ repository لجميع الأبناء. بما أن شجرتي الـ widgets الخاصة بصفحة `EditTodoPage` وصفحة `HomePage` هي منحدرات لـ `App`، فيمكن لجميع الـ blocs والـ cubits الوصول إلى الـ repository. @@ -376,7 +376,7 @@ repository. #### HomeCubit -الـ cubit مناسب هنا بسبب بساطة منطق العمل. لدينا دالة واحدة `setTab` لتغيير +الـ cubit مناسب هنا بسبب بساطة منطق الأعمال. لدينا دالة واحدة `setTab` لتغيير التبويب. -تمثيل مبسط لشجرة الويجت الخاصة بـ `HomePage` هو: +تمثيل مبسط لشجرة Widget الخاصة بـ `HomePage` هو: تقدم `HomePage` نسخة من `HomeCubit` إلى `HomeView`. يستخدم `HomeView` الدالة `context.select` لإعادة البناء بشكل انتقائي كلما تغير التبويب. هذا يسمح لنا -باختبار الويجت `HomeView` بسهولة عن طريق توفير نسخة Mock من `HomeCubit` وتمثيل +باختبار Widget `HomeView` بسهولة عن طريق توفير نسخة Mock من `HomeCubit` وتمثيل الحالة (stubbing state). شريط التطبيق السفلي (`BottomAppBar`) يحتوي على أزرار `HomeTabButton` التي تستدعي @@ -545,7 +545,7 @@ repository. يوجد ملف نموذج واحد يتعامل مع تصفية العرض. -`todos_view_filter.dart` هو enum يمثل الثلاثة فلاتر للرؤية بالإضافة إلى الدوال +`todos_view_filter.dart` هو enum يمثل مرشحات العرض الثلاثة بالإضافة إلى الدوال لتطبيق الفلتر. -تمثيل مبسط لشجرة الويجت الخاصة بـ `TodosOverviewPage` هو: +تمثيل مبسط لشجرة Widget الخاصة بـ `TodosOverviewPage` هو: تمامًا مثل ميزة الصفحة الرئيسية، تقوم `TodosOverviewPage` بتوفير نسخة من `TodosOverviewBloc` للشجرة الفرعية عبر `BlocProvider`، مما -يقتصر نطاق هذا الـ bloc على الويجتات الموجودة تحت `TodosOverviewPage` فقط. +يقتصر نطاق هذا الـ bloc على Widgets الموجودة تحت `TodosOverviewPage` فقط. هناك ثلاث ويدجتات تستمع للتغييرات في `TodosOverviewBloc`. @@ -606,7 +606,7 @@ repository. title="lib/todos_overview/view/view.dart" /> -#### الويجتات (Widgets) +#### Widgets `widgets.dart` هو ملف تجميعي آخر يصدر جميع المكونات المستخدمة ضمن ميزة `todos_overview`. @@ -692,7 +692,7 @@ repository. title="lib/stats/view/stats_page.dart" /> -تمثيل مبسط لشجرة الويجت الخاصة بـ `StatsPage` هو: +تمثيل مبسط لشجرة Widget الخاصة بـ `StatsPage` هو: @@ -700,7 +700,7 @@ repository. كل من `TodosOverviewBloc` و `StatsBloc` يتواصلان مع `TodosRepository`, لكن من المهم ملاحظة أنه لا يوجد تواصل مباشر بين الـ blocs. راجع قسم -[تدفق البيانات](#تدفق-البيانات-data-flow#data-flow) لمزيد من التفاصيل. +[تدفق البيانات](#تدفق-البيانات-data-flow) لمزيد من التفاصيل. ::: @@ -759,7 +759,7 @@ repository. عند إرسال واجهة المستخدم حدث `EditTodoSubmitted`: -- `EditTodoBloc` يتولى منطق العمل بتحديث الـ `TodosRepository`. +- `EditTodoBloc` يتولى منطق الأعمال بتحديث الـ `TodosRepository`. - `TodosRepository` يُبلغ كل من `TodosOverviewBloc` و `StatsBloc`. - `TodosOverviewBloc` و `StatsBloc` يُبلغان الواجهة التي تقوم بالتحديث وفقًا للحالة الجديدة. @@ -776,7 +776,7 @@ repository. توفر طريقة `static` اسمها `route`. هذا يسهل دفع الصفحة على مكدس التنقل عبر `Navigator.of(context).push(...)`. -تمثيل مبسط لشجرة الويجت الخاصة بـ `EditTodosPage` هو: +تمثيل مبسط لشجرة Widget الخاصة بـ `EditTodosPage` هو: @@ -784,6 +784,6 @@ repository. هذا كل شيء، لقد أكملنا الدرس التعليمي! 🎉 -يمكنك العثور على الكود المصدري الكامل لهذا المثال، بما في ذلك اختبارات الوحدة -والـ widget، +يمكنك العثور على الأكواد البرمجية المصدرية الكاملة لهذا المثال، بما في ذلك +اختبارات الوحدة واختبارات Widgets، [هنا](https://github.com/felangel/bloc/tree/master/examples/flutter_todos). diff --git a/docs/src/content/docs/ar/tutorials/flutter-weather.mdx b/docs/src/content/docs/ar/tutorials/flutter-weather.mdx index a8777e6067c..239a8cb9abc 100644 --- a/docs/src/content/docs/ar/tutorials/flutter-weather.mdx +++ b/docs/src/content/docs/ar/tutorials/flutter-weather.mdx @@ -1,6 +1,6 @@ --- title: تطبيق الطقس Flutter -description: دليل متعمق حول كيفية بناء تطبيق طقس في Flutter باستخدام bloc. +description: دليل متعمّق لبناء تطبيق طقس في Flutter باستخدام مكتبة Bloc. sidebar: order: 5 --- @@ -29,17 +29,17 @@ import WeatherBarrelDartSnippet from '~/components/tutorials/flutter-weather/Wea ![advanced](https://img.shields.io/badge/level-advanced-red.svg) -في هذا الدرس، سنقوم ببناء تطبيق طقس باستخدام Flutter يوضح كيفية إدارة عدة cubits -لتنفيذ التخصيص الديناميكي للثيمات، والسحب للتحديث، والعديد من الميزات الأخرى. -سيعتمد تطبيق الطقس على جلب بيانات الطقس الحية من واجهة برمجة التطبيقات العامة -OpenMeteo، وسنشرح كيفية فصل تطبيقنا إلى طبقات (البيانات، المستودع، منطق العمل، +في هذا الدرس، سنبني تطبيق طقس في Flutter يوضح كيفية إدارة عدة cubits لتنفيذ +التخصيص الديناميكي للثيمات، والسحب للتحديث، والعديد من الميزات الأخرى. سيعتمد +تطبيق الطقس على جلب بيانات الطقس الحية من واجهة برمجة التطبيقات العامة +OpenMeteo، وسنشرح كيفية فصل التطبيق إلى طبقات (البيانات، المستودع، منطق الأعمال، وطبقة العرض). ![demo](~/assets/tutorials/flutter-weather.gif) ## متطلبات المشروع -يجب أن تتيح تطبيقنا للمستخدمين ما يلي: +يجب أن يتيح تطبيقنا للمستخدمين ما يلي: - البحث عن مدينة من خلال صفحة مخصصة للبحث - عرض تمثيل بصري مريح لبيانات الطقس التي يتم جلبها من @@ -56,26 +56,26 @@ OpenMeteo، وسنشرح كيفية فصل تطبيقنا إلى طبقات (ا ## المفاهيم الرئيسية - مراقبة تغييرات الحالة باستخدام [BlocObserver](/ar/bloc-concepts#blocobserver). -- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، ويدجت Flutter التي +- [BlocProvider](/ar/flutter-bloc-concepts#blocprovider)، وهي Widget في Flutter توفر bloc لأبنائها. -- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، ويدجت Flutter التي تتولى - بناء الودجت استجابةً للحالات الجديدة. +- [BlocBuilder](/ar/flutter-bloc-concepts#blocbuilder)، وهي Widget في Flutter + تتولى إعادة البناء استجابةً للحالات الجديدة. - تجنب عمليات إعادة البناء غير الضرورية باستخدام [Equatable](/ar/faqs/#متى-يجب-استخدام-equatable). -- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، ويدجت - Flutter التي توفر repository لأبنائها. -- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، ويدجت Flutter التي +- [RepositoryProvider](/ar/flutter-bloc-concepts#repositoryprovider)، وهي Widget + في Flutter توفر repository لأبنائها. +- [BlocListener](/ar/flutter-bloc-concepts#bloclistener)، وهي Widget في Flutter تستدعي كود المستمع استجابةً لتغيرات الحالة في الـ bloc. -- [MultiBlocProvider](/ar/flutter-bloc-concepts#multiblocprovider)، ويدجت - Flutter التي تجمع عدة BlocProvider في واحد. -- [BlocConsumer](/ar/flutter-bloc-concepts#blocconsumer)، ويدجت Flutter التي +- [MultiBlocProvider](/ar/flutter-bloc-concepts#multiblocprovider)، وهي Widget + في Flutter التي تجمع عدة `BlocProvider` في واحد. +- [BlocConsumer](/ar/flutter-bloc-concepts#blocconsumer)، وهي Widget في Flutter توفر builder و listener للاشتراك في حالات جديدة. - [HydratedBloc](https://github.com/felangel/bloc/tree/master/packages/hydrated_bloc) لإدارة الحالة وحفظها بشكل مستمر. ## الإعداد -لبداية العمل، قم بإنشاء مشروع flutter جديد +لبداية العمل، أنشئ مشروع Flutter جديدًا. @@ -360,7 +360,7 @@ OpenMeteo، وسنشرح كيفية فصل تطبيقنا إلى طبقات (ا #### اختبارات عميل API بعدها، لنختبر عميل API الخاص بنا. يجب أن نتحقق من أن عميل API يتعامل بشكل صحيح -مع كلا طلبات الـ API، بما في ذلك السيناريوهات الحدية (Edge Cases). +مع كل طلبات الـ API، بما في ذلك السيناريوهات الحدية (Edge Cases). :::note @@ -384,15 +384,15 @@ OpenMeteo، وسنشرح كيفية فصل تطبيقنا إلى طبقات (ا -## طبقة الريبو (Repository Layer) +## طبقة المستودع (Repository Layer) -الهدف من طبقة الريبو لدينا هو تجريد طبقة البيانات وتسهيل الاتصال مع طبقة الـ -bloc. عند القيام بذلك، يعتمد بقية الكود الأساسي لدينا فقط على الدوال المعروضة من -خلال طبقة الريبو بدلاً من الاعتماد على تنفيذات مزود البيانات المحددة. هذا يسمح -لنا بتغيير مزودي البيانات دون التأثير على أي كود في مستوى التطبيق. على سبيل -المثال، إذا قررنا الانتقال بعيدًا عن واجهة برمجة التطبيقات الخاصة بالطقس التي -نستخدمها حالياً، يجب أن نتمكن من إنشاء عميل API جديد واستبداله دون الحاجة لتعديل -واجهة برمجة التطبيقات العامة للريبو أو طبقات التطبيق. +الهدف من طبقة المستودع هو تجريد طبقة البيانات وتسهيل الاتصال مع طبقة الـ bloc. +عند القيام بذلك، يعتمد بقية الكود الأساسي لدينا فقط على الدوال المعروضة من خلال +طبقة المستودع بدلًا من الاعتماد على تنفيذات مزود البيانات المحددة. هذا يسمح لنا +بتغيير مزودي البيانات دون التأثير على أي كود في مستوى التطبيق. على سبيل المثال، +إذا قررنا الانتقال بعيدًا عن واجهة برمجة التطبيقات الخاصة بالطقس التي نستخدمها +حالياً، يجب أن نتمكن من إنشاء عميل API جديد واستبداله دون الحاجة لتعديل واجهة +API العامة لطبقة المستودع أو طبقات التطبيق. ### الإعداد @@ -502,8 +502,8 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس ### اختبارات الوحدة -تمامًا كما هو الحال مع طبقة البيانات، من الضروري اختبار طبقة الريبو لضمان صحة -منطق الأعمال/المنطق على مستوى المجال. لاختبار `WeatherRepository`، سنستخدم مكتبة +تمامًا كما هو الحال مع طبقة البيانات، من الضروري اختبار طبقة المستودع لضمان صحة +منطق المجال. لاختبار `WeatherRepository`، سنستخدم مكتبة [mocktail](https://github.com/felangel/mocktail). سنقوم بمحاكاة عميل الـ API الأساسي لاختبار منطق `WeatherRepository` ضمن بيئة مختبرية ومعزولة. @@ -538,8 +538,8 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس - استخدام [equatable](https://pub.dev/packages/equatable) يتيح لمثيلات حالة التطبيق إمكانية المقارنة باستخدام معامل المساواة `==`. في الخلفية، يقوم bloc بمقارنة الحالات ليرى إذا ما كانت متساوية، وإذا لم تكن كذلك، سيؤدي ذلك إلى - إعادة بناء. هذا يضمن أن شجرة الـ widget ستُعاد بناؤها فقط عند الضرورة للحفاظ - على الأداء سريعًا ومتجاوبًا. + إعادة بناء. هذا يضمن أن شجرة Widget ستُعاد بناؤها فقط عند الضرورة للحفاظ على + الأداء سريعًا ومتجاوبًا. - يمكننا تحسين واجهة المستخدم باستخدام حزمة [google_fonts](https://pub.dev/packages/google_fonts). - توفر [HydratedBloc](https://pub.dev/packages/hydrated_bloc) إمكانية حفظ حالة @@ -550,7 +550,7 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس لأغراض الاختبار، سنضيف الحزم المعتادة `test`، بالإضافة إلى `mocktail` لتقليد التبعيات، و [bloc_test](https://pub.dev/packages/bloc_test) -لتسهيل اختبار وحدات منطق العمل أو الـ blocs! +لتسهيل اختبار وحدات منطق الأعمال أو الـ blocs! json)`، `toJson(WeatherState state)` تُستخدم للحفظ والاستعادة @@ -658,7 +658,7 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس :::note -لا تنسى توليد كود (de)serialization عبر: +لا تنسَ توليد كود (de)serialization عبر: ::: @@ -696,15 +696,15 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس ### صفحة الطقس -سنبدأ بـ `WeatherPage` التي تستخدم `BlocProvider` من أجل توفير نسخة من -`WeatherCubit` لشجرة الويدجت. +سنبدأ بـ `WeatherPage` التي تستخدم `BlocProvider` لتوفير instance من +`WeatherCubit` لشجرة Widget. -ستلاحظ أن هذه الصفحة تعتمد على ويدجت `SettingsPage` و `SearchPage`، اللتين +ستلاحظ أن هذه الصفحة تعتمد على Widget `SettingsPage` و`SearchPage`، اللتين سننشئهما بعد ذلك. ### صفحة الإعدادات @@ -726,9 +726,9 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس title="lib/search/view/search_page.dart" /> -### ويدجتات الطقس +### Widgets الطقس -ستعرض التطبيق شاشات مختلفة اعتمادًا على الحالات الأربع المحتملة لـ +سيعرض التطبيق شاشات مختلفة اعتمادًا على الحالات الأربع المحتملة لـ `WeatherCubit`. #### حالة WeatherEmpty @@ -786,7 +786,7 @@ bloc. عند القيام بذلك، يعتمد بقية الكود الأساس title="lib/main.dart" /> -ويدجت `app.dart` يتولى بناء عرض `WeatherPage` الذي أنشأناه سابقًا ويستخدم +Widget `app.dart` تتولى بناء عرض `WeatherPage` الذي أنشأناه سابقًا وتستخدم `BlocProvider` لحقن الـ `WeatherCubit`. -### اختبارات الويدجت +### اختبارات Widgets توفّر مكتبة [`bloc_test`](https://pub.dev/packages/bloc_test) أيضًا `MockBlocs` -و `MockCubits` التي تجعل من السهل اختبار واجهة المستخدم. يمكننا تقمص حالات الـ +و `MockCubits` التي تجعل من السهل اختبار واجهة المستخدم. يمكننا محاكاة حالات الـ cubits المختلفة والتأكد من استجابة واجهة المستخدم بشكل صحيح. -وأخيرًا، نحتاج إلى تثبيت التبعيات. +أخيرًا، نحتاج إلى تثبيت dependencies. -هذا كل ما يتعلق بإعداد المشروع! الآن يمكننا البدء في بناء حزمة -`common_github_search`. +انتهى إعداد المشروع. الآن يمكننا البدء في بناء حزمة `common_github_search`. -### عميل GitHub +### Github Client -الـ `GithubClient` هو المسؤول عن توفير البيانات الخام من -[واجهة برمجة تطبيقات GitHub](https://developer.github.com/v3/). +`GithubClient` سيكون مسؤولًا عن توفير البيانات الخام من +[GitHub API](https://developer.github.com/v3/). :::note -يمكنك الاطلاع على نموذج للبيانات التي نحصل عليها من +يمكنك رؤية مثال لشكل البيانات العائدة من API [هنا](https://api.github.com/search/repositories?q=dartlang). ::: -لننشئ الملف `github_client.dart`. +لننشئ `github_client.dart`. -الآن بعد أن أكملنا تنفيذ `SearchResult` واعتمادياته، سننتقل إلى +بهذا نكون انتهينا من تنفيذ `SearchResult` واعتمادياته، وننتقل الآن إلى `SearchResultError`. #### نموذج خطأ نتيجة البحث @@ -163,15 +161,13 @@ AngularDart لشرح كيفية مشاركة طبقات البيانات ومن title="lib/src/models/search_result_error.dart" /> -لقد انتهينا من `GithubClient`، لذلك سننتقل إلى `GithubCache`، الذي سيكون مسؤولًا -عن -[التخزين المؤقت للنتائج (Memoization)](https://en.wikipedia.org/wiki/Memoization) -كوسيلة لتحسين الأداء. +انتهينا من `GithubClient`، والخطوة التالية هي `GithubCache`، والذي سيكون مسؤولًا +عن [memoization](https://en.wikipedia.org/wiki/Memoization) كتحسين للأداء. -### التخزين المؤقت لـ GitHub +### GitHub Cache -سيتولى `GithubCache` مهمة تذكر جميع الاستعلامات السابقة لتجنب إجراء طلبات شبكة -غير ضرورية إلى API GitHub. هذا سيُساعد أيضًا على تحسين أداء التطبيق. +سيكون `GithubCache` مسؤولًا عن تذكّر جميع الاستعلامات السابقة لتجنب تنفيذ طلبات +شبكة غير ضرورية إلى GitHub API. هذا يساعد أيضًا في تحسين أداء التطبيق. أنشئ `github_cache.dart`. @@ -180,12 +176,12 @@ AngularDart لشرح كيفية مشاركة طبقات البيانات ومن title="lib/src/github_cache.dart" /> -الآن نحن مستعدون لإنشاء `GithubRepository`! +الآن أصبحنا جاهزين لإنشاء `GithubRepository`. -### مستودع GitHub +### GitHub Repository -مستودع GitHub مسؤول عن خلق طبقة تجريدية بين طبقة البيانات (`GithubClient`) وطبقة -منطق الأعمال (`Bloc`). هذه الطبقة أيضًا المكان الذي سنستخدم فيه `GithubCache`. +`GithubRepository` مسؤول عن إنشاء طبقة تجريد (abstraction) بين طبقة البيانات +(`GithubClient`) وطبقة منطق الأعمال (`Bloc`). وهنا أيضًا سنستخدم `GithubCache`. أنشئ `github_repository.dart`. @@ -196,19 +192,19 @@ AngularDart لشرح كيفية مشاركة طبقات البيانات ومن :::note -يعتمد `GithubRepository` على `GithubCache` و `GithubClient` ويقوم بتجريد التنفيذ -الداخلي. تطبيقنا لا يحتاج لمعرفة كيفية استرجاع البيانات أو مصدرها لأنه لا يهمه -ذلك. يمكننا تغيير طريقة عمل المستودع في أي وقت طالما أننا لم نغير الواجهة -(interface) فليس هناك حاجة لتعديل أي كود يستخدمه. +`GithubRepository` يعتمد على `GithubCache` و`GithubClient`، ويخفي تفاصيل التنفيذ +الداخلية. التطبيق لا يحتاج لمعرفة كيفية جلب البيانات أو مصدرها. يمكننا تغيير +طريقة عمل الـ repository في أي وقت، وما دمنا لم نغيّر الواجهة (interface)، فلن +نحتاج لتعديل كود العملاء (client code). ::: -الآن بعد أن أكملنا طبقة مزود البيانات وطبقة المستودع، نحن جاهزون للانتقال إلى -طبقة منطق الأعمال. +بهذا نكون أكملنا طبقة مزوّد البيانات وطبقة الـ repository، وأصبحنا جاهزين +للانتقال إلى طبقة منطق الأعمال. -### حدث بحث GitHub +### حدث GitHub Search -سيتم إعلام الـ Bloc عندما يقوم المستخدم بكتابة اسم مستودع، والذي سنمثله عبر حدث +سيتم إشعار Bloc عندما يكتب المستخدم اسم repository، وسنمثل ذلك عبر حدث `TextChanged` من نوع `GithubSearchEvent`. أنشئ `github_search_event.dart`. @@ -220,24 +216,27 @@ AngularDart لشرح كيفية مشاركة طبقات البيانات ومن :::note -نحن نستخدم الوراثة من [`Equatable`](https://pub.dev/packages/equatable) لتمكين -مقارنة مثيلات `GithubSearchEvent`. بشكل افتراضي، عامل المساواة يعيد true فقط إذا -كانت هذه والمثيل الآخر نفس الكائن. +نرث من [`Equatable`](https://pub.dev/packages/equatable) حتى نتمكن من مقارنة نسخ +`GithubSearchEvent`. افتراضيًا، عامل المساواة يعيد `true` فقط إذا كان الكائنان +نفس النسخة. ::: -### حالة بحث GitHub +### حالة GitHub Search + +طبقة العرض تحتاج عدة حالات لتتمكن من رسم الواجهة بشكل صحيح: + +- `SearchStateEmpty` لإبلاغ طبقة العرض بعدم وجود إدخال من المستخدم. +- `SearchStateLoading` لإبلاغ طبقة العرض بضرورة إظهار مؤشر تحميل. +- `SearchStateSuccess` لإبلاغ طبقة العرض بوجود بيانات جاهزة للعرض. -طبقة العرض لدينا ستحتاج إلى مجموعة من المعلومات لكي تعرض الواجهة بشكل صحيح: + - `items` ستكون `List` التي ستُعرض. -- `SearchStateEmpty`- تخبر طبقة العرض أنه لم يتم إدخال أي مدخلات من المستخدم. -- `SearchStateLoading`- تخبر طبقة العرض بضرورة عرض مؤشر تحميل. -- `SearchStateSuccess`- تخبر طبقة العرض بأنها تمتلك بيانات للعرض. - - `items`- ستكون قائمة من `List` التي سيتم عرضها. -- `SearchStateError`- تخبر طبقة العرض بحدوث خطأ أثناء جلب البيانات. - - `error`- هو الخطأ المحدد الذي حدث. +- `SearchStateError` لإبلاغ طبقة العرض بحدوث خطأ أثناء جلب repositories. -يمكننا الآن إنشاء `github_search_state.dart` وتنفيذه كما يلي. + - `error` يمثل الخطأ الفعلي الذي حدث. + +يمكننا الآن إنشاء `github_search_state.dart` وتنفيذه كالتالي. -بعدها، نحتاج لتحديث ملف `pubspec.yaml` ليشمل جميع الاعتمادات اللازمة. +بعد ذلك نحدّث `pubspec.yaml` ليتضمن كل dependencies المطلوبة. -هذا كل شيء بخصوص إعداد المشروع. بما أن مكتبة `common_github_search` تحتوي على -طبقة البيانات وطبقة منطق العمل، ما علينا بناؤه هو فقط طبقة العرض. +انتهى إعداد المشروع. بما أن `common_github_search` تحتوي طبقة البيانات وطبقة +منطق الأعمال، فكل ما نحتاج لبنائه هو طبقة العرض. ### نموذج البحث -سنحتاج لإنشاء نموذج يحتوي على عناصر واجهة `_SearchBar` و `_SearchBody`. +سنحتاج إلى إنشاء نموذج يحتوي `widget` باسم `_SearchBar` و`widget` باسم +`_SearchBody`. -- `_SearchBar` مسؤول عن استقبال إدخال المستخدم. -- `_SearchBody` مسؤول عن عرض نتائج البحث، مؤشرات التحميل، والأخطاء. +- `_SearchBar` سيكون مسؤولًا عن استقبال إدخال المستخدم. +- `_SearchBody` سيكون مسؤولًا عن عرض نتائج البحث ومؤشرات التحميل والأخطاء. -لننشئ ملف `search_form.dart`. +لننشئ `search_form.dart`. - +`SearchForm` سيكون `StatelessWidget` يعرض `_SearchBar` و`_SearchBody`. + +`_SearchBar` سيكون أيضًا `StatefulWidget` لأنه يحتاج لإدارة +`TextEditingController` خاص به حتى نتتبع مدخلات المستخدم. -لقد انتهينا من `SearchForm`! الآن نحتاج لتنفيذ `_SearchBar` و `_SearchBody`. +`_SearchBody` هو `StatelessWidget` مسؤول عن عرض نتائج البحث والأخطاء ومؤشرات +التحميل. وهو المستهلك لـ `GithubSearchBloc`. -### شريط البحث +إذا كانت الحالة `SearchStateSuccess`، سنعرض `_SearchResults` الذي سننفذه لاحقًا. -`_SearchBar` هو `StatefulWidget` لأنه سيحتاج إلى إدارة `TextEditingController`. +`_SearchResults` هو `StatelessWidget` يستقبل `List` ويعرضها +كقائمة من `_SearchResultItems`. + +`_SearchResultItem` هو `StatelessWidget` مسؤول عن عرض بيانات نتيجة بحث واحدة. +وهو أيضًا مسؤول عن التعامل مع تفاعل المستخدم والتنقل إلى رابط repository عند +النقر. :::note -عندما يتغير النص، نقوم بإضافة حدث `TextChanged` إلى `GithubSearchBloc`. +`_SearchBar` يصل إلى `GithubSearchBloc` عبر `context.read()` +ويُشعِر bloc بأحداث `TextChanged`. ::: -### جسم البحث +:::note -`_SearchBody` هو `StatelessWidget` مسؤول عن عرض نتائج البحث بناءً على حالة الـ -Bloc. +`_SearchBody` يستخدم `BlocBuilder` لإعادة البناء استجابةً لتغيّر الحالة. وبما أن +وسيط bloc في `BlocBuilder` تم حذفه، سيقوم `BlocBuilder` تلقائيًا بالبحث عن +instance مناسبة عبر `BlocProvider` و`BuildContext` الحالي. اقرأ المزيد +[هنا.](/ar/flutter-bloc-concepts#blocbuilder) - +::: :::note -نحن نستخدم `BlocBuilder` لإعادة بناء الواجهة في كل مرة تتغير فيها حالة -`GithubSearchBloc`. +نستخدم `ListView.builder` لبناء قائمة قابلة للتمرير من `_SearchResultItem`. ::: -### نتائج البحث +:::note -`_SearchResults` هو `StatelessWidget` يعرض قائمة من `SearchResultItem`. +نستخدم حزمة [url_launcher](https://pub.dev/packages/url_launcher) لفتح الروابط +الخارجية. - +::: -### عنصر نتيجة البحث +### تجميع كل شيء -`_SearchResultItem` هو `StatelessWidget` يعرض تفاصيل مستودع واحد ويفتح الرابط -عند النقر. +كل ما تبقى هو تنفيذ التطبيق الرئيسي في `main.dart`. -### تجميع كل شيء معًا +:::note -أخيرًا، نحتاج لتعديل `main.dart`. +يتم إنشاء `GithubRepository` في `main` وحقنه داخل `App`. ثم يتم تغليف +`SearchForm` داخل `BlocProvider` المسؤول عن إنشاء instance من `GithubSearchBloc` +وإغلاقها وإتاحتها لـ `SearchForm` والأبناء. - +::: + +بهذا نكون نفذنا تطبيق بحث GitHub في Flutter بنجاح باستخدام حزمتَي +[bloc](https://pub.dev/packages/bloc) و +[flutter_bloc](https://pub.dev/packages/flutter_bloc)، وتمكنا من فصل طبقة العرض +عن طبقة منطق الأعمال. -هذا كل شيء! لقد انتهينا من تطبيق Flutter. الآن سننتقل إلى AngularDart. +يمكنك العثور على المصدر الكامل +[هنا](https://github.com/felangel/bloc/tree/master/examples/github_search/flutter_github_search). + +الآن سننتقل إلى بناء تطبيق GitHub Search باستخدام AngularDart. ## بحث GitHub باستخدام AngularDart -تطبيق AngularDart GitHub Search سيعيد استخدام نفس منطق العمل من -`common_github_search`. +AngularDart GitHub Search سيكون تطبيق AngularDart يعيد استخدام النماذج، ومزوّدي +البيانات، والـ repositories، والـ blocs من `common_github_search` لتنفيذ ميزة +البحث في GitHub. ### الإعداد -أنشئ مشروع AngularDart جديد. +نبدأ بإنشاء مشروع AngularDart جديد داخل مجلد `github_search` في نفس مستوى +`common_github_search`. + + + +:::note + +يمكنك تثبيت `stagehand` عبر: - +::: -حدث ملف `pubspec.yaml`. +بعدها يمكننا استبدال محتوى `pubspec.yaml` بما يلي: -ثبت التبعيات. +### نموذج البحث + +كما في تطبيق Flutter، نحتاج إلى `SearchForm` يحتوي على مكوّني `SearchBar` و +`SearchBody`. - +مكوّن `SearchForm` سيطبّق `OnInit` و`OnDestroy` لأنه يحتاج لإنشاء +`GithubSearchBloc` ثم إغلاقه. -### شريط البحث (Search Bar) +- `SearchBar` مسؤول عن استقبال إدخال المستخدم. +- `SearchBody` مسؤول عن عرض نتائج البحث ومؤشرات التحميل والأخطاء. + +لننشئ `search_form_component.dart`. + + + +:::note + +يتم حقن `GithubRepository` داخل `SearchFormComponent`. + +::: + +:::note + +`SearchFormComponent` هو المسؤول عن إنشاء `GithubSearchBloc` وإغلاقه. + +::: + +وسيكون القالب (`search_form_component.html`) كالتالي: + + + +الآن سننفذ مكوّن `SearchBar`. + +### Search Bar + +`SearchBar` هو مكوّن مسؤول عن استقبال إدخال المستخدم، وإشعار `GithubSearchBloc` +بتغيّر النص. أنشئ `search_bar_component.dart`. @@ -429,18 +483,25 @@ Bloc. title="angular_github_search/lib/src/search_form/search_bar/search_bar_component.dart" /> -والقالب `search_bar_component.html`. +:::note + +`SearchBarComponent` يعتمد على `GithubSearchBloc` لأنه مسؤول عن إرسال أحداث +`TextChanged` إلى bloc. + +::: + +بعدها ننشئ `search_bar_component.html`. -لقد انتهينا من `SearchBar`، الآن ننتقل إلى `SearchBody`. +انتهينا من `SearchBar`، والآن ننتقل إلى `SearchBody`. -### جسم البحث (Search Body) +### Search Body -`SearchBody` هو مكون مسؤول عن عرض نتائج البحث، الأخطاء، ومؤشرات التحميل. سيكون +`SearchBody` هو مكوّن مسؤول عن عرض نتائج البحث والأخطاء ومؤشرات التحميل. وهو المستهلك لـ `GithubSearchBloc`. أنشئ `search_body_component.dart`. @@ -452,24 +513,24 @@ Bloc. :::note -`SearchBodyComponent` يعتمد على `GithubSearchState` والذي يتم توفيره من خلال -`GithubSearchBloc` باستخدام أنبوب الـ bloc في `angular_bloc`. +`SearchBodyComponent` يعتمد على `GithubSearchState` التي يتم توفيرها من +`GithubSearchBloc` باستخدام bloc pipe الخاصة بـ `angular_bloc`. ::: -أنشئ القالب `search_body_component.html`. +أنشئ `search_body_component.html`. -إذا كانت الحالة `isSuccess`، نقوم بعرض `SearchResults`. سنقوم بتنفيذه لاحقًا. +إذا كانت الحالة `isSuccess`، سنعرض `SearchResults`، وسننفيذه الآن. -### نتائج البحث (Search Results) +### Search Results -`SearchResults` هو مكون يستقبل قائمة من `List` ويعرضها على -هيئة قائمة من عناصر `SearchResultItem`. +`SearchResults` هو مكوّن يستقبل `List` ويعرضها كقائمة من +`SearchResultItems`. أنشئ `search_results_component.dart`. @@ -478,7 +539,7 @@ Bloc. title="angular_github_search/lib/src/search_form/search_body/search_results/search_results_component.dart" /> -بعدها، سننشئ القالب `search_results_component.html`. +بعد ذلك ننشئ `search_results_component.html`. -والقالب المرتبط به في `search_result_item_component.html`. +ثم أنشئ القالب المقابل `search_result_item_component.html`. -### تجميع كل شيء معًا +### تجميع كل شيء -لدينا الآن جميع مكوناتنا وحان الوقت لتجميعها كلها معًا في `app_component.dart`. +لدينا الآن جميع المكونات، وحان وقت تجميعها داخل `app_component.dart`. -ثم قم بإنشاء مشروع جديد عبر: +ثم أنشئ مشروعًا جديدًا عبر: -بعد ذلك، يمكننا استبدال محتويات ملف `pubspec.yaml` بما يلي: +بعد ذلك، استبدل محتوى ملف `pubspec.yaml` بما يلي: -ثم نقوم بتثبيت جميع التبعيات (Dependencies) الخاصة بنا: +ثم ثبّت جميع dependencies: -سيحتوي تطبيق العداد الخاص بنا على زرين لزيادة/إنقاص قيمة العداد وعنصر لعرض -القيمة الحالية. لنبدأ بتصميم `CounterEvents`. +سيحتوي تطبيق العداد على زرين لزيادة/إنقاص قيمة العداد، وعنصر لعرض القيمة +الحالية. لنبدأ بتصميم `CounterEvents`. ## Counter Bloc -نظرًا لأنه يمكن تمثيل حالة العداد لدينا بعدد صحيح (integer)، لا نحتاج إلى إنشاء -فئة مخصصة، ويمكننا تجميع الأحداث (events) و Bloc في مكان واحد. +بما أن حالة العداد يمكن تمثيلها بعدد صحيح (`integer`)، فلا حاجة لإنشاء class +مخصصة، ويمكننا وضع events وBloc في نفس المكان. -ويجب أن يبدو ملف `app.component.html` الخاص بنا كما يلي: +ويجب أن يكون `app.component.html` كالتالي: