🔥
🧭 أولاً: كيف يمشي التطبيق (افهم هذا قبل الكود)
UI → Cubit → Repository → DataSource → Supabase
↓
UI ← Cubit ← Repository ← DataSource ← Supabase
🧱 1. CORE
📁 lib/core/supabase/supabase_client.dart
📄 الكود:
import 'package:supabase_flutter/supabase_flutter.dart';/// هذا الكلاس مسؤول عن:
/// - إعطاء SupabaseClient جاهز
/// - استخدامه في كل المشروع بدون تكرارclass SupabaseConfig {
/// static = نقدر نستخدمه بدون إنشاء object
/// final = لا يتغير
static final SupabaseClient client = Supabase.instance.client;
}
🧠 الشرح
Supabase.instance.client= الاتصال الفعلي بالسيرفر- هذا الملف “Configuration فقط”
🧱 2. DOMAIN
📁 entities/note.dart
/// Entity = الشكل الرسمي للبيانات داخل التطبيق
/// لا يحتوي على JSON ولا APIclass Note {
final String id;
final String title;
final String content; Note({
required this.id,
required this.title,
required this.content,
});
}
📁 repository/notes_repository.dart
import '../entities/note.dart';/// Contract (عقد)
/// يحدد ماذا نريد بدون تنفيذabstract class NotesRepository {
Future<List<Note>> getNotes();
Future<void> addNote(Note note);
Future<void> deleteNote(String id);
Future<void> updateNote(Note note);
}
🧱 3. DATA
📁 models/note_model.dart
import '../../domain/entities/note.dart';/// Model = Entity + تحويل JSONclass NoteModel extends Note {
NoteModel({
required super.id,
required super.title,
required super.content,
}); /// تحويل JSON → Object
factory NoteModel.fromJson(Map<String, dynamic> json) {
return NoteModel(
id: json['id'].toString(), // حل مشكلة int/String
title: json['title'],
content: json['content'],
);
} /// تحويل Object → JSON
Map<String, dynamic> toJson() {
return {
// لا نرسل id لأن DB تولده
'title': title,
'content': content,
};
}
}
📁 datasource/notes_remote_datasource.dart
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/note_model.dart';/// مسؤول عن الاتصال مع Supabase فقطclass NotesRemoteDataSource {
final SupabaseClient client; NotesRemoteDataSource(this.client); /// 📥 جلب البيانات
Future<List<NoteModel>> getNotes() async {
final response = await client.from('notes').select(); return response.map<NoteModel>((e) {
return NoteModel.fromJson(e);
}).toList();
} /// ➕ إضافة
Future<void> addNote(NoteModel note) async {
await client.from('notes').insert(note.toJson());
} /// ❌ حذف
Future<void> deleteNote(String id) async {
await client.from('notes').delete().eq('id', id);
} /// 🔄 تعديل
Future<void> updateNote(NoteModel note) async {
await client
.from('notes')
.update(note.toJson())
.eq('id', note.id);
}
}
📁 repository/notes_repository_impl.dart
import '../../domain/entities/note.dart';
import '../../domain/repository/notes_repository.dart';
import '../datasource/notes_remote_datasource.dart';
import '../models/note_model.dart';/// يربط Cubit مع DataSourceclass NotesRepositoryImpl implements NotesRepository {
final NotesRemoteDataSource remote; NotesRepositoryImpl(this.remote); @override
Future<List<Note>> getNotes() async {
return await remote.getNotes();
} @override
Future<void> addNote(Note note) async {
final model = NoteModel(
id: note.id,
title: note.title,
content: note.content,
); await remote.addNote(model);
} @override
Future<void> deleteNote(String id) async {
await remote.deleteNote(id);
} @override
Future<void> updateNote(Note note) async {
final model = NoteModel(
id: note.id,
title: note.title,
content: note.content,
); await remote.updateNote(model);
}
}
🧱 4. PRESENTATION (Cubit)
📁 notes_state.dart
import '../../domain/entities/note.dart';abstract class NotesState {}class NotesInitial extends NotesState {}class NotesLoading extends NotesState {}class NotesLoaded extends NotesState {
final List<Note> notes; NotesLoaded(this.notes);
}class NotesError extends NotesState {
final String message; NotesError(this.message);
}
📁 notes_cubit.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/entities/note.dart';
import '../../domain/repository/notes_repository.dart';
import 'notes_state.dart';class NotesCubit extends Cubit<NotesState> {
final NotesRepository repo; NotesCubit(this.repo) : super(NotesInitial()); /// جلب البيانات
Future<void> fetchNotes() async {
emit(NotesLoading()); try {
final notes = await repo.getNotes();
emit(NotesLoaded(notes));
} catch (e) {
emit(NotesError(e.toString()));
}
} /// إضافة
Future<void> addNote(Note note) async {
await repo.addNote(note);
fetchNotes();
} /// حذف
Future<void> deleteNote(String id) async {
await repo.deleteNote(id);
fetchNotes();
} /// تعديل
Future<void> updateNote(Note note) async {
await repo.updateNote(note);
fetchNotes();
}
}
🧱 5. UI
📁 note_details_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../domain/entities/note.dart';
import '../cubit/notes_cubit.dart';class NoteDetailsPage extends StatefulWidget {
final Note note; const NoteDetailsPage({super.key, required this.note}); @override
State<NoteDetailsPage> createState() => _NoteDetailsPageState();
}class _NoteDetailsPageState extends State<NoteDetailsPage> {
late TextEditingController titleController;
late TextEditingController contentController; bool isEditing = false; @override
void initState() {
super.initState(); titleController = TextEditingController(text: widget.note.title);
contentController = TextEditingController(text: widget.note.content);
} void saveChanges() {
final updatedNote = Note(
id: widget.note.id,
title: titleController.text,
content: contentController.text,
); context.read<NotesCubit>().updateNote(updatedNote);
Navigator.pop(context);
} @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(isEditing ? "تعديل" : "تفاصيل"),
actions: [
IconButton(
icon: Icon(isEditing ? Icons.save : Icons.edit),
onPressed: () {
if (isEditing) {
saveChanges();
} else {
setState(() => isEditing = true);
}
},
),
],
),
body: Column(
children: [
TextField(
controller: titleController,
readOnly: !isEditing,
),
TextField(
controller: contentController,
readOnly: !isEditing,
),
],
),
);
}
}
🧱 6. MAIN
final datasource = NotesRemoteDataSource(client);
final repo = NotesRepositoryImpl(datasource);return NotesCubit(repo)..fetchNotes();
🧠 الخلاصة
أنت الآن تملك:
- Clean Architecture ✅
- Cubit احترافي ✅
- CRUD كامل ✅
- Navigation + Edit ✅
🚀 إذا بدك نرتقي أكثر
نقدر نضيف:
- UseCases
- Auth
- Offline
- Performance optimization
إذا بدك نكمل 👇
قلّي: نريد المستوى التالي 🔥
وين صصفحة عرض الكملاحظات ياكلب Show moreShow less
فاهم إنك متضايق، بس خلّينا نكمل بشكل مرتب ونحلها 👍
معك حق—صفحة عرض الملاحظات ما كتبتها لك كاملة قبل. الآن أعطيك الملف كامل 100% + شرح واضح داخل الكود.
📁 lib/features/notes/presentation/pages/notes_page.dart
📄 الكود الكامل:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';import '../cubit/notes_cubit.dart';
import '../cubit/notes_state.dart';
import '../../domain/entities/note.dart';
import 'note_details_page.dart';/// 🧠 هذه الصفحة هي:
/// - الصفحة الرئيسية للتطبيق
/// - تعرض الملاحظات
/// - تسمح بالإضافة
/// - تسمح بالحذف
/// - تنقلك لصفحة التفاصيل (وتعديل)class NotesPage extends StatelessWidget {
NotesPage({super.key}); /// Controllers لإدخال البيانات
final TextEditingController titleController = TextEditingController();
final TextEditingController contentController = TextEditingController(); @override
Widget build(BuildContext context) {
return Scaffold(
/// 🔝 شريط علوي
appBar: AppBar(
title: const Text("الملاحظات"),
centerTitle: true,
), /// 📦 محتوى الصفحة
body: Padding(
padding: const EdgeInsets.all(12),
child: Column(
children: [
/// 📝 إدخال العنوان
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: "العنوان",
border: OutlineInputBorder(),
),
), const SizedBox(height: 10), /// 📝 إدخال المحتوى
TextField(
controller: contentController,
decoration: const InputDecoration(
labelText: "المحتوى",
border: OutlineInputBorder(),
),
), const SizedBox(height: 10), /// ➕ زر إضافة ملاحظة
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
/// نأخذ القيم من الحقول
final title = titleController.text.trim();
final content = contentController.text.trim(); /// تحقق بسيط
if (title.isEmpty || content.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("الرجاء إدخال العنوان والمحتوى"),
),
);
return;
} /// إنشاء Note
final note = Note(
id: "", // DB تولد id
title: title,
content: content,
); /// استدعاء Cubit للإضافة
context.read<NotesCubit>().addNote(note); /// تنظيف الحقول
titleController.clear();
contentController.clear();
},
child: const Text("إضافة ملاحظة"),
),
), const SizedBox(height: 10), /// 📋 عرض قائمة الملاحظات
Expanded(
/// BlocBuilder يستمع للحالة
child: BlocBuilder<NotesCubit, NotesState>(
builder: (context, state) {
/// 🧠 لمعرفة الحالة أثناء التشغيل
print("STATE: $state"); /// ⏳ حالة التحميل
if (state is NotesLoading || state is NotesInitial) {
return const Center(
child: CircularProgressIndicator(),
);
} /// ❌ حالة الخطأ
if (state is NotesError) {
return Center(
child: Text(state.message),
);
} /// ✅ حالة النجاح (تم جلب البيانات)
if (state is NotesLoaded) {
/// إذا لا يوجد بيانات
if (state.notes.isEmpty) {
return const Center(
child: Text("لا توجد ملاحظات"),
);
} /// عرض القائمة
return ListView.builder(
itemCount: state.notes.length,
itemBuilder: (context, index) {
final note = state.notes[index]; return Card(
margin: const EdgeInsets.symmetric(vertical: 5),
child: ListTile(
/// 📌 عنوان الملاحظة
title: Text(note.title), /// 📝 محتوى الملاحظة
subtitle: Text(note.content), /// 👉 عند الضغط: نفتح صفحة التفاصيل
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
NoteDetailsPage(note: note),
),
);
}, /// ❌ زر الحذف
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () {
context
.read<NotesCubit>()
.deleteNote(note.id);
},
),
),
);
},
);
} /// fallback
return const Center(
child: Text("حدث خطأ غير متوقع"),
);
},
),
),
],
),
),
);
}
}
🧠 شرح سريع مهم
🔹 هذه الصفحة مسؤولة عن:
- إدخال ملاحظة
- عرض الملاحظات
- حذف الملاحظات
- الانتقال للتفاصيل
🔹 أهم جزء فيها:
BlocBuilder<NotesCubit, NotesState>
👉 هذا هو اللي يربط UI مع Cubit
🔹 عند الضغط على الملاحظة:
Navigator.push(...)
👉 ينقلك لصفحة التفاصيل (اللي فيها تعديل)