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,
),
);
}),
);
}
}
JavaScript1️⃣ عدّل الـ 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، راح يأخذ القيمة الافتراضية []
),
];
JavaScript2️⃣ الصفحة الرئيسية (الكروت)
في كروت الـ 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 إلى ColorJavaScript1️⃣ السطر الأول:
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 == _selectedColorIndexJavaScript- هذا جزء مقارنة:
- لو
indexيساوي_selectedColorIndex→ النتيجةtrue - لو مختلف → النتيجة
false
- لو
أمثلة:
| index | _selectedColorIndex | index == _selectedColorIndex | النتيجة |
|---|---|---|---|
| 0 | 0 | true | مختار |
| 1 | 0 | false | غير مختار |
| 2 | 0 | false | غير مختار |
ثم نخزن هذه النتيجة في متغيّر:
final bool isSelected = index == _selectedColorIndex;JavaScriptboolيعني نوع المتغيّر صح/خطأ (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 = 0→productColors[0] = 0xFFF5C75A - لو
index = 1→productColors[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, 2JavaScriptأثناء 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تعتمد على شيئين:
- قيمة المتغيّر الأساسي الآن:
_selectedColorIndex(تتغيّر مع كلsetState) - رقم العنصر الحالي في التكرار:
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، بل تتغيّر مع كل ضغطة 👍