إضافة الالوان للمنتج

1️⃣ تعديل الـ Model (لو لسه ما فيه ألوان)

لنفرض إن عندك Product بهذا الشكل (مثال):

class Product {
  final String title;
  final String image;
  final String description;

  Product({
    required this.title,
    required this.image,
    required this.description,
  });
}
JavaScript

نضيف له حقل خاص بالألوان، وليكن قائمة أرقام int تمثل ألوان ARGB:

class Product {
  final String title;
  final String image;
  final String description;

  // ✅ الكود المضاف: قائمة ألوان لكل منتج
  final List<int> colors;

  Product({
    required this.title,
    required this.image,
    required this.description,
    required this.colors, // ← لا تنسَ تضيفها هنا
  });
}
JavaScript

استخدمنا List<int> بدل List<Color> لأن أسهل شيء تخزن الألوان كقيمة رقمية مثل 0xFFF5C75A في ملف الـ data،
وبعدين نحولها إلى Color() في الواجهة.

2️⃣ تزويد الألوان في ملف data

في ملف البيانات (مثلاً data.dart أو أيًا كان اسمه):

final List<Product> products = [
  Product(
    title: 'ساعة أبل واتش سيريس 9',
    image: 'assets/images/apple_watch.png',
    description: 'ساعة أبل الذكية المميزة ...',
    colors: [
      0xFFF5C75A, // ذهبي
      0xFF1C1C1E, // أسود
      0xFFD0D4DC, // فضي
    ],
  ),
  // منتجات أخرى ...
];
JavaScript

كل منتج تعطيه الألوان المناسبة له في الـ colors.

3️⃣ ربط الألوان بالـ Widget في ProductDetailsPage

الآن نرجع لصفحة التفاصيل اللي عندك، ونخليها تستخدم ألوان المنتج بدل القائمة اللي كنتُ حاطّها يدويًا.

الكود الكامل (مع توضيح أماكن التعديل)
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import '../models/productModel.dart';

class ProductDetailsPage extends StatefulWidget {
  final Product productDetails;

  ProductDetailsPage({required this.productDetails});

  @override
  State<ProductDetailsPage> createState() => _ProductDetailsPageState();
}

class _ProductDetailsPageState extends State<ProductDetailsPage> {
  // ✅ يحدد أي لون مختار من قائمة ألوان المنتج
  int _selectedColorIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: [
            Container(
              height: 350,
              width: double.infinity,
              color: Colors.red,
              child: Image.asset(
                widget.productDetails.image,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            ),
            const Gap(20),

            // ✅ الكود المضاف: ويدجت اختيار الألوان من الـ model
            _buildColorSelector(context),
            const Gap(20),

            Text(
              widget.productDetails.title,
              textAlign: TextAlign.center,
              style: const TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const Gap(20),
            Padding(
              padding: const EdgeInsets.all(10),
              child: Text(
                widget.productDetails.description,
                textAlign: TextAlign.center,
              ),
            ),
            Divider(color: Colors.grey[300]),
          ],
        ),
      ),
    );
  }

  // ✅ هذه الودجت الآن تقرأ الألوان من widget.productDetails.colors
  Widget _buildColorSelector(BuildContext context) {
    final productColors = widget.productDetails.colors;

    // لو المنتج ما عنده ألوان (قائمة فاضية) ما نعرض شيء
    if (productColors.isEmpty) {
      return const SizedBox.shrink();
    }

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: List.generate(productColors.length, (index) {
        final bool isSelected = index == _selectedColorIndex;
        final Color color = Color(productColors[index]); // ← تحويل int إلى Color

        return GestureDetector(
          onTap: () {
            setState(() {
              _selectedColorIndex = index;
            });
          },
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            margin: const EdgeInsets.symmetric(horizontal: 6),
            width: 32,
            height: 32,
            decoration: BoxDecoration(
              color: color,
              borderRadius: BorderRadius.circular(8),
              border: Border.all(
                width: isSelected ? 2.5 : 1,
                color: isSelected
                    ? Theme.of(context).colorScheme.primary
                    : Colors.grey.shade400,
              ),
            ),
            child: isSelected
                ? const Icon(
                    Icons.check,
                    size: 18,
                    color: Colors.white,
                  )
                : null,
          ),
        );
      }),
    );
  }
}
JavaScript

1️⃣ عدّل الـ Model بحيث يكون colors غير إجباري
class Product {
  final String title;
  final String image;
  final String description;

  // ✅ حقل الألوان – اختياري، افتراضيًا قائمة فارغة
  final List<int> colors;

  const Product({
    required this.title,
    required this.image,
    required this.description,
    this.colors = const [], // 👈 لم نعد نستخدم required هنا
  });
}
JavaScript
  • كذا:
    • في الصفحة الرئيسية تقدر تنشئ Product بدون ما تكتب colors نهائيًا.
    • لو منتج عنده ألوان → تضيفها في ملف الـ data فقط لهذا المنتج.

مثال في ملف الـ data:

final products = [
  Product(
    title: 'ساعة أبل واتش سيريس 9',
    image: 'assets/images/apple_watch.png',
    description: 'ساعة أبل الذكية المميزة...',
    colors: [
      0xFFF5C75A,
      0xFF1C1C1E,
      0xFFD0D4DC,
    ], // 👈 هذا المنتج عنده ألوان
  ),
  Product(
    title: 'منتج بدون ألوان',
    image: 'assets/images/other.png',
    description: 'هذا المنتج ما عنده خيارات ألوان.',
    // 👈 هنا ما كتبنا colors، راح يأخذ القيمة الافتراضية []
  ),
];
JavaScript
2️⃣ الصفحة الرئيسية (الكروت)

في كروت الـ Home لا تعمل أي شيء له علاقة بالألوان،
فقط استخدم title, image, description مثل أول، ولا تغيّر شيء.

// مثال عام
Card(
  child: Column(
    children: [
      Image.asset(product.image),
      Text(product.title),
      Text(product.description),
    ],
  ),
);
JavaScript

ما في داعي تذكر product.colors هنا.

3️⃣ صفحة التفاصيل (نفس الودجت اللي عملناها)

الكود اللي شرحتُه لك يبقى كما هو تقريبًا، لأنه أصلاً يتعامل مع حالة عدم وجود ألوان:

Widget _buildColorSelector(BuildContext context) {
  final productColors = widget.productDetails.colors;

  // لو ما فيه ألوان، لا تعرض شيء
  if (productColors.isEmpty) {
    return const SizedBox.shrink();
  }

  return Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: List.generate(productColors.length, (index) {
      final bool isSelected = index == _selectedColorIndex;
      final Color color = Color(productColors[index]);

      return GestureDetector(
        onTap: () {
          setState(() {
            _selectedColorIndex = index;
          });
        },
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 200),
          margin: const EdgeInsets.symmetric(horizontal: 6),
          width: 32,
          height: 32,
          decoration: BoxDecoration(
            color: color,
            borderRadius: BorderRadius.circular(8),
            border: Border.all(
              width: isSelected ? 2.5 : 1,
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Colors.grey.shade400,
            ),
          ),
          child: isSelected
              ? const Icon(
                  Icons.check,
                  size: 18,
                  color: Colors.white,
                )
              : null,
        ),
      );
    }),
  );
}
JavaScript
  • لو المنتج ما عنده ألوان → colors تكون []
    → الشرط if (productColors.isEmpty) يتحقّق
    → ما يتم عرض أي شيء تحت الصورة 👌
خلاصة سريعة
  • colors الآن حقل اختياري في الموديل، مو required.
  • في الصفحة الرئيسية تجاهل الألوان تمامًا (لا تعرضها).
  • في صفحة التفاصيل فقط، تقرأ product.colors؛ لو فيه بيانات تعرض مربعات الألوان، لو ما فيه ما تعرض شيء.

نركّز على هذين السطرين سطر–سطر وبالتفصيل 👇

final bool isSelected = index == _selectedColorIndex;
final Color color = Color(productColors[index]); // ← تحويل int إلى Color
JavaScript
1️⃣ السطر الأول:
final bool isSelected = index == _selectedColorIndex;
JavaScript
🔹 أين نكون داخل الكود؟

هذا السطر موجود داخل:

List.generate(productColors.length, (index) {
  // هنا...
});
JavaScript

يعني:

  • index هو رقم العنصر الحالي في اللستة (0، 1، 2، …).
  • _selectedColorIndex هو المتغير الموجود في الـ state ويحفظ أي لون اختاره المستخدم.

مثلاً:

  • عندك 3 ألوان ⇒ productColors.length = 3
  • أوّل مرة: _selectedColorIndex = 0 (اللون الأول هو المختار افتراضيًا).

🔹 ماذا يفعل السطر؟

index == _selectedColorIndex
JavaScript
  • هذا جزء مقارنة:
    • لو index يساوي _selectedColorIndex → النتيجة true
    • لو مختلف → النتيجة false

أمثلة:

index_selectedColorIndexindex == _selectedColorIndexالنتيجة
00trueمختار
10falseغير مختار
20falseغير مختار

ثم نخزن هذه النتيجة في متغيّر:

final bool isSelected = index == _selectedColorIndex;
JavaScript
  • bool يعني نوع المتغيّر صح/خطأ (Boolean).
  • اسم المتغيّر: isSelected (هل هو مختار؟)
  • final يعني:
    • لا نغيّر قيمة isSelected بعد هذا السطر داخل نفس البناء (build لهذا العنصر).
    • لكن في إعادة بناء الواجهة (بعد setState) يحسب من جديد.
🔹 كيف نستخدم isSelected لاحقًا؟
border: Border.all(
  width: isSelected ? 2.5 : 1,
  color: isSelected
      ? Theme.of(context).colorScheme.primary
      : Colors.grey.shade400,
),
child: isSelected
    ? const Icon(Icons.check, size: 18, color: Colors.white)
    : null,
JavaScript
  • لو isSelected == true:
    • سماكة الحد 2.5
    • لون الحد أساسي (مثلاً أزرق)
    • نعرض علامة ✅ داخل المربع
  • لو isSelected == false:
    • سماكة الحد 1
    • لون الحد رمادي
    • لا نعرض أيقونة داخل المربع

إذًا:

هذا السطر وظيفته يحدد “هل هذا المربع هو المربّع المختار حاليًا أم لا؟”

2️⃣ السطر الثاني:
final Color color = Color(productColors[index]);
JavaScript

🔹 ما هو productColors؟

قبل هذا السطر، غالبًا عندك:

final productColors = widget.productDetails.colors;
JavaScript

وفي الـ model:

final List<int> colors;
JavaScript

يعني:

  • productColors هي List<int> = قائمة من الأرقام، كل رقم يمثل لون بالشكل:
    0xFF000000, 0xFFFFFFFF, 0xFFF5C75A, …
  • هذا هو الشكل العادي لألوان ARGB في Flutter (int هكس).

مثال:

productColors = [
  0xFFF5C75A, // ذهبي
  0xFF1C1C1E, // أسود
  0xFFD0D4DC, // فضي
];
JavaScript
🔹 ماذا يفعل productColors[index]؟
  • يأخذ العنصر رقم index من القائمة.
  • لو index = 0productColors[0] = 0xFFF5C75A
  • لو index = 1productColors[1] = 0xFF1C1C1E

إذًا:

productColors[index]
JavaScript

يعطيك int يمثل لون.

🔹 تحويل int إلى Color

Flutter لا يفهم الرقم وحده كـ Widget، هو يحتاج كائن من نوع Color.

وهنا يأتي دور:

Color(productColors[index])
JavaScript
  • هذا يستدعي الـ constructor الخاص بكلاس Color.
  • يأخذ int (مثل 0xFFF5C75A) ويحوله إلى كائن من نوع Color.

ثم نخزن النتيجة في متغيّر:

final Color color = Color(productColors[index]);
JavaScript
  • نوع المتغيّر: Color
  • الاسم: color
  • القيمة: كائن Color يُمثِّل اللون الخاص بهذا المؤشر من القائمة.
🔹 كيف نستخدم color بعدها؟

مثلاً داخل BoxDecoration:

decoration: BoxDecoration(
  color: color, // ← هنا
  borderRadius: BorderRadius.circular(8),
  border: Border.all(...),
),
JavaScript

هذا السطر هو اللي يلوّن خلفية المربع باللون المناسب لكل عنصر.

تلخيص الجملتين بكلمة بسيطة

1️⃣

final bool isSelected = index == _selectedColorIndex;
JavaScript

يحدد إذا كان هذا المربع هو المربع المختار حاليًا أم لا (صح/خطأ)

2️⃣

final Color color = Color(productColors[index]);
JavaScript

يأخذ اللون المخزن كرقم في اللستة، ويحوّله إلى كائن Color لكي نستخدمه في تصميم المربع

🕒 1) في البداية (أول مرة تُبنى الصفحة)

في الـ State عندك:

int _selectedColorIndex = 0; // القيمة الافتراضية
JavaScript

ولنفترض عندك 3 ألوان:

productColors = [لون1, لون2, لون3]; // indices: 0, 1, 2
JavaScript

أثناء build، داخل List.generate:

دورة الألوان:
  • لما index = 0: isSelected = (0 == 0); // => true ✅ المربع الأول يعتبر مختار (حد سميك + علامة ✓)
  • لما index = 1: isSelected = (1 == 0); // => false ❌ المربع الثاني غير مختار
  • لما index = 2: isSelected = (2 == 0); // => false ❌ المربع الثالث غير مختار

النتيجة:
أول ما تفتح الصفحة: اللون الأول هو المختار افتراضيًا، وهذا منطقي.

🖱️ 2) لما المستخدم يضغط على مربع لون معيّن

في كل مربع عندك:

GestureDetector(
  onTap: () {
    setState(() {
      _selectedColorIndex = index;
    });
  },
  child: ...
)
JavaScript

افترض المستخدم ضغط على المربع الثالث (اللي index = 2):

داخل onTap يصير الآتي:

setState(() { _selectedColorIndex = 2; // لم يعد 0 الآن!<br>});<br>
JavaScript

النقطة المهمّة:
_selectedColorIndex ما يبقى 0 طول الوقت،
بل يتغيّر إلى قيمة الـ index حق المربع الذي ضغط عليه المستخدم.

بعد setState، Flutter يعيد استدعاء build() من جديد بالكامل 👇

🔁 3) إعادة البناء بعد الضغط

الآن _selectedColorIndex أصبح 2 بدل 0.

نرجع لنفس المقارنة:

دورة الألوان من جديد:
  • لما index = 0: isSelected = (0 == 2); // => false ❌ المربع الأول الآن غير مختار
  • لما index = 1: isSelected = (1 == 2); // => false ❌ الثاني غير مختار
  • لما index = 2: isSelected = (2 == 2); // => true ✅ المربع الثالث أصبح هو المختار

وبالتالي:

  • يظهر الحد السميك + علامة ✓ على المربع الثالث
  • يختفي التحديد عن الأول والثاني
🔁 4) كل ضغطة = تغيير قيمة المتغيّر الأساسي

فكرة السطر:

final bool isSelected = index == _selectedColorIndex;
JavaScript

تعتمد على شيئين:

  1. قيمة المتغيّر الأساسي الآن:
    _selectedColorIndex (تتغيّر مع كل setState)
  2. رقم العنصر الحالي في التكرار:
    index

في البداية:

  • _selectedColorIndex = 0 → اللون الأول مختار

بعد الضغط على لون ثاني مثلًا (index = 1):

  • _selectedColorIndex يصبح 1
  • المقارنة تتغيّر
    فيصير اللون الثاني هو المختار وهكذا…
🎯 تشبيه بسيط

تخيل عندك 3 أزرار، وعندك متغيّر اسمه currentButton = 0.

  • أول مرة: تخلي الزر رقم 0 لونه مختلف (لأنه يطابق currentButton).
  • لما تضغط الزر رقم 2:
    • تغيّر currentButton إلى 2
    • تعيد رسم الأزرار
    • أي زر رقمه يساوي currentButton ⇐ هو المميز.

نفس الشيء بالضبط هنا، لكن باستخدام Flutter و setState.

لو حاب، أقدر أضيف لك print داخل onTap عشان تشوف بعينك في الـ console:

onTap: () {
  setState(() {
    _selectedColorIndex = index;
    print('Selected index: $_selectedColorIndex');
  });
}
JavaScript

وبتشوف إنه فعلاً القيمة ما تبقى 0، بل تتغيّر مع كل ضغطة 👍