الفصل 07Chapter 07

التنقّل والتوجيه بمرونة Navigation & Adaptive Overlays

كيف يتحرك المستخدم بين الشاشات، وكيف نصمم واجهات تتغير بناءً على حجم الشاشة. How users move between screens and how to design UI that adapts to screen sizes.

🚀 ليست مجرد شاشات More Than Just Screens

في Flutter، التنقل بيعتمد على مفهوم الـ Stack (العامود). لما تفتح شاشة جديدة، إنت بتحطها "فوق" الشاشة الحالية. ولما ترجع، بتشيلها من فوق.

In Flutter, navigation is based on the Stack concept. When you open a new screen, you "push" it on top of the current one. When you go back, you "pop" it off.

المشروع ده بيستخدم Navigator 1.0 التقليدي مع GlobalKey للتحكم من أي مكان.

🛡️ بوابات الدخول: Auth Guard Logic Entry Gates: Conditional Routing

التطبيق مش دايماً بيفتح على الـ Dashboard. فيه "بوابات" لازم المستخدم يعدي عليها بالترتيب:

1
Privacy Consent

لو دي أول مرة يفتح التطبيق، لازم يوافق على سياسة الخصوصية.

2
Auth Guard

لو مفعل البصمة، لازم يتأكد من هويته قبل ما يشوف البيانات.

3
Main Screen

أخيراً يوصل للشاشة الرئيسية.

📁 lib/main.dart
home: prefs.isPrivacyAccepted
    ? (settings.isBiometricEnabled
          ? const AuthGuardScreen()
          : const MainScreen())
    : const PrivacyConsentScreen(),
📱 القوائم المرنة: Adaptive Overlays Responsive UI: Adaptive Overlays

في الـ MRE CashBook، إحنا مهتمين جداً إن شكل القوائم (Dialogs/Sheets) يكون مريح. على الموبايل بتظهر من تحت (Bottom Sheet)، وعلى التابلت بتظهر في نص الشاشة (Dialog).

📁 lib/core/utils/overlays/adaptive_dialogs.dart
static Future showAdaptiveModal(BuildContext context, {required Widget child}) {
  if (ResponsiveLayout.isMobile(context)) {
    return _showMobileSheet(context, child: child);
  }
  return _showDesktopDialog(context, child: child);
}
ResponsiveLayout.isMobile(context)

هنا الكود "بيشم" حجم الشاشة. لو لقى العرض صغير، بيشغل showModalBottomSheet. لو العرض كبير، بيشغل showGeneralDialog مع أنيميشن احترافي.

💼 الحقيبة: MaterialPageRoute ومعاملات الشاشة The Briefcase: MaterialPageRoute & Arguments

لما بننتقل لشاشة تفاصيل كتاب معين، لازم نبعت معانا "الشنطة" اللي فيها بيانات الكتاب ده. إزاي بنعمل كدة في الـ 1.0؟

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => BookDetailsScreen(book: currentBook),
    settings: const RouteSettings(name: '/book-details'),
  ),
);

لاحظ إننا استخدمنا الـ Constructor بتاع الشاشة مباشرة. دي أسهل وأأمن طريقة (Type-Safe) عشان تتأكد إن الشاشة استلمت الـ Object الصح.

إحنا كمان بنسمي الـ Route في RouteSettings عشان لو حد حب يتتبع المستخدم (Analytics) يعرف هو واقف في أنهي شاشة.

🕸️ شبكة الأمان: PopScope وزر الرجوع The Safety Net: PopScope & Back Buttons

تخيل إنت بتكتب عملية حسابية طويلة، وفجأة دوست "رجوع" بالغلط. في MRE CashBook، بنستخدم PopScope (البديل الحديث لـ WillPopScope) عشان نمنع الكارثة دي.

return PopScope(
  canPop: false, // بنقفل الرجوع التلقائي
  onPopInvokedWithResult: (didPop, result) async {
    if (didPop) return;
    final shouldPop = await _showConfirmExitDialog();
    if (shouldPop && context.mounted) {
      Navigator.of(context).pop();
    }
  },
  child: Scaffold(...),
);

دي حركة احترافية جداً بتخلي المستخدم يتأكد إنه مش هيخسر بياناته اللي لسه معملهاش Save.

🔙 رحلة العودة: الرجوع بالبيانات The Return Trip: Navigating Back with Data

أحياناً إنت بتبعت المستخدم يختار حاجة من شاشة تانية (زي الـ Date Picker أو الـ Category). لما بيرجع، لازم يرجع ومعاه "الهدية".

// في الشاشة اللي بنختار منها
Navigator.pop(context, selectedCategory);

// في الشاشة الأصلية اللي مستنية
final result = await Navigator.push(context, route);
if (result != null) {
  _updateUI(result);
}

ده بيخلي التواصل بين الشاشات شغال في الاتجاهين (Two-way Communication).

🏗️ المتحول: التنقل بين الموبايل والتابلت The Shapeshifter: Mobile vs Tablet Navigation

في MRE CashBook، إحنا مش بس بنغير حجم الـ Container، إحنا بنغير "طريقة التنقل" نفسها بناءً على حجم الشاشة.

DeviceWidget UsedBehavior
MobileBottomNavigationBarأيقونات تحت الإبهام عشان سهولة الوصول.
Tablet / DesktopNavigationRailشريط جانبي بيدي مساحة أكبر للمحتوى في النص.
Scaffold(
  body: Row(
    children: [
      if (!ResponsiveLayout.isMobile(context))
        const AppNavigationRail(), // الشريط الجانبي للتابلت
      const Expanded(child: MainView()),
    ],
  ),
  bottomNavigationBar: ResponsiveLayout.isMobile(context)
      ? const AppBottomNavigationBar() // الشريط السفلي للموبايل
      : null,
);

ده بيخلي التطبيق يبدو كأنه تطبيق تابلت "حقيقي" مش مجرد تطبيق موبايل "ممطوط".

السحر البصري: Hero و Animations Visual Magic: Hero & Custom Animations

التنقل الناشف بيخلي التطبيق يحسسك إنه "قديم". إحنا بنستخدم الـ Hero عشان البيانات "تطير" من شاشة للتانية.

// في شاشة القائمة
Hero(
  tag: 'book-${book.id}',
  child: BookIcon(book),
);

// في شاشة التفاصيل
Hero(
  tag: 'book-${book.id}',
  child: LargeBookIcon(book),
);

بمجرد ما تستخدم نفس الـ tag، فلاتر بيكلف نفسه إنه يعمل أنيميشن ناعم جداً يحسس المستخدم إن الشاشة بتكبر مش بتبدل.

📝 الملخص Summary

التنقل السلس والواجهة المرنة هما الفرق بين تطبيق "شغال" وتطبيق "احترافي".

الفصل السابقPrevious الفصل التاليNext