البحث عن الملاحظات

✅ البحث محلياً (الأفضل حالياً)
نجيب كل الملاحظات مرة

نفلتر داخل Cubit

نحدّث UI
Dart
🧠 لماذا اخترنا البحث المحلي؟
✔ أسرع
✔ لا يحتاج API إضافي
✔ مناسب لتطبيقك الحالي
Dart
🧱 التعديل سيكون فقط في:
Cubit
State
UI
Dart

❌ لا نعدل DataSource
❌ لا نعدل Repository
❌ لا نعدل UseCase

🧱 1) State
📁 notes_state.dart
✏️ نضيف:
class NotesLoaded extends NotesState {
  final List<Note> notes;

  NotesLoaded(this.notes);
}
Dart
🧠 لماذا لا نضيف State جديد؟
لأن البحث فقط تغيير في البيانات
وليس حالة جديدة (Loading / Error)
Dart
🧱 2) Cubit (أهم جزء 🔥)
📁 notes_cubit.dart
📄 الكود + شرح داخلي
class NotesCubit extends Cubit<NotesState> {

  final GetNotesUseCase getNotesUseCase;

  /// 🔥 قائمة أصلية (كل البيانات)
  List<Note> allNotes = [];

  NotesCubit(this.getNotesUseCase) : super(NotesInitial());

  /// جلب البيانات
  Future<void> fetchNotes() async {
    emit(NotesLoading());

    try {
      final notes = await getNotesUseCase();

      /// نخزن النسخة الأصلية
      allNotes = notes;

      emit(NotesLoaded(notes));
    } catch (e) {
      emit(NotesError(e.toString()));
    }
  }

  /// 🔥 البحث
  void searchNotes(String query) {

    /// إذا المستخدم مسح البحث
    if (query.isEmpty) {
      emit(NotesLoaded(allNotes));
      return;
    }

    /// فلترة
    final filtered = allNotes.where((note) {

      return note.title.toLowerCase().contains(query.toLowerCase()) ||
             note.content.toLowerCase().contains(query.toLowerCase());

    }).toList();

    emit(NotesLoaded(filtered));
  }
}
Dart
🧠 شرح مفصل جداً
🔹 allNotes
نسخة احتياطية من البيانات
Dart

👉 ليش؟

لأنه بعد الفلترة بنخسر البيانات الأصلية
Dart
🔹 searchNotes
دالة تأخذ النص وتفلتر
Dart
🔹 where
allNotes.where(...)
Dart

👉 يعني:

جيب العناصر التي تحقق الشرط
Dart
🔹 contains
note.title.contains(query)
Dart

👉 يعني:

هل النص يحتوي الكلمة؟
Dart
🔹 toLowerCase
لجعل البحث غير حساس لحالة الأحرف
Dart
🧱 3) UI (إضافة SearchBar)
📁 notes_page.dart
📄 الكود
Column(
  children: [

    /// 🔍 البحث
    Padding(
      padding: const EdgeInsets.all(10),
      child: TextField(
        decoration: InputDecoration(
          hintText: "ابحث...",
          prefixIcon: Icon(Icons.search),
          border: OutlineInputBorder(),
        ),

        /// 👇 عند الكتابة
        onChanged: (value) {
          context.read<NotesCubit>().searchNotes(value);
        },
      ),
    ),

    /// القائمة
    Expanded(
      child: BlocBuilder<NotesCubit, NotesState>(
        builder: (context, state) {

          if (state is NotesLoading) {
            return Center(child: CircularProgressIndicator());
          }

          if (state is NotesLoaded) {

            if (state.notes.isEmpty) {
              return Center(child: Text("لا توجد نتائج"));
            }

            return GridView.builder(
              itemCount: state.notes.length,
              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
              ),
              itemBuilder: (context, index) {
                final note = state.notes[index];
                return NoteCard(note: note);
              },
            );
          }

          return SizedBox();
        },
      ),
    ),
  ],
)
Dart
🧠 ماذا يحدث الآن؟
تكتب في TextField

onChanged

Cubit.searchNotes()

فلترة

emit(NotesLoaded)

UI يتحدث
Dart
💥 مثال عملي
عندك:
["hello", "flutter", "notes"]
Dart
كتبت:
"fl"
Dart
النتيجة:
["flutter"]
Dart
🧠 تحسين احترافي (اختياري)
debounce (تأخير البحث)

حتى لا ينفذ كل حرف:

يمنع الضغط على الأداء
Dart
💥 خلاصة قوية
search = فلترة محلية
Cubit = العقل
UI = الإدخال والعرض
Dart
🧠 الجملة الذهبية
Search is filtering, not fetching
Dart
🧭 الفكرة العامة
UI (TextField)

Cubit.searchNotes()

UseCase

Repository

DataSource

Supabase (query with filter)

يرجع النتائج

UI يعرضها
Dart
🧠 الفرق عن البحث السابق
النوعكيف يعمل
Local Searchفلترة داخل التطبيق
Supabase Searchquery من السيرفر
🧱 1) DataSource (أهم خطوة 🔥)
📁 notes_remote_datasource.dart
📄 الكود
Future<List<NoteModel>> searchNotes(String query) async {

  /// ilike = بحث غير حساس للأحرف (case insensitive)
  final response = await client
      .from('notes')
      .select()
      .or('title.ilike.%$query%,content.ilike.%$query%');

  /// تحويل List<dynamic> → List<NoteModel>
  return (response as List)
      .map((e) => NoteModel.fromJson(e))
      .toList();
}
Dart
🧠 شرح مفصل جداً
🔹 .or()
.or('title.ilike.%query%,content.ilike.%query%')
Dart

👉 معناها:

ابحث في title أو content
Dart
🔹 ilike
بحث بدون حساسية للأحرف
Dart
🔹 %query%
% = wildcard
يعني يحتوي على الكلمة
Dart
🧱 2) Repository
📁 notes_repository.dart
Future<List<Note>> searchNotes(String query);
Dart
🧠 ليش؟
Domain يعرف العملية فقط
Dart
🧱 3) RepositoryImpl
📁 notes_repository_impl.dart
@override
Future<List<Note>> searchNotes(String query) async {
  return await remote.searchNotes(query);
}
Dart
🧠 شرح
يمرر الطلب فقط
Dart
🧱 4) UseCase
📁 search_notes_usecase.dart
import '../entities/note.dart';
import '../repository/notes_repository.dart';

class SearchNotesUseCase {
  final NotesRepository repo;

  SearchNotesUseCase(this.repo);

  Future<List<Note>> call(String query) async {
    return await repo.searchNotes(query);
  }
}
Dart
🧠 شرح
يمثل عملية البحث
Dart
🧱 5) Cubit
📁 notes_cubit.dart
final SearchNotesUseCase searchNotesUseCase;

NotesCubit(
  this.getNotesUseCase,
  this.addNoteUseCase,
  this.deleteNoteUseCase,
  this.updateNoteUseCase,
  this.searchNotesUseCase,
) : super(NotesInitial());

/// 🔥 البحث من السيرفر
Future<void> searchNotes(String query) async {

  /// إذا فاضي رجع كل البيانات
  if (query.isEmpty) {
    fetchNotes();
    return;
  }

  emit(NotesLoading());

  try {
    final results = await searchNotesUseCase(query);

    emit(NotesLoaded(results));
  } catch (e) {
    emit(NotesError(e.toString()));
  }
}
Dart
🧠 شرح
🔹 لماذا emit(Loading)؟
لأننا ننتظر API
Dart
🔹 لماذا fetchNotes عند empty؟
لإعادة القائمة الأصلية
Dart
🧱 6) UI
📁 notes_page.dart
TextField(
  decoration: InputDecoration(
    hintText: "ابحث...",
    prefixIcon: Icon(Icons.search),
  ),

  onChanged: (value) {
    context.read<NotesCubit>().searchNotes(value);
  },
),
Dart
🧠 ماذا يحدث الآن؟
تكتب

Cubit.searchNotes

Supabase query

يرجع النتائج

UI يتحدث
Dart
💥 مثال
المستخدم كتب:
"flutter"
Dart
Supabase ينفذ:
WHERE title ILIKE '%flutter%'
OR content ILIKE '%flutter%'
Dart
⚠️ مهم جداً
🔴 كثرة الطلبات

كل حرف = request 😱

🔥 الحل الاحترافي (Debounce)
داخل Cubit:
Timer? _debounce;

void searchNotes(String query) {
  _debounce?.cancel();

  _debounce = Timer(const Duration(milliseconds: 400), () async {

    if (query.isEmpty) {
      fetchNotes();
      return;
    }

    emit(NotesLoading());

    final results = await searchNotesUseCase(query);
    emit(NotesLoaded(results));
  });
}
Dart
🧠 لماذا؟
نمنع إرسال request لكل حرف
Dart
💥 الخلاصة
Search API =
CubitUseCaseRepoDataSourceSupabase
Dart
🧠 الجملة الذهبية
Server search = query, not filter
Dart