watchCourseTable method

Stream<CourseTableData> watchCourseTable({
  1. required int semesterId,
})

Watches the course schedule for a semester with automatic background refresh.

Emits cached data immediately, then triggers a background network fetch if data is empty or stale. The stream re-emits automatically when the DB is updated.

Use watchCourseOffering for related data (teachers, classrooms, schedules).

Implementation

Stream<CourseTableData> watchCourseTable({required int semesterId}) async* {
  const ttl = Duration(days: 3);

  final query = _database.select(_database.courseTableSlots)
    ..where((s) => s.semester.equals(semesterId));

  await for (final rows in query.watch()) {
    final allOfferingRows =
        await (_database.select(
          _database.courseOfferings,
        )..where((o) => o.semester.equals(semesterId))).join([
          leftOuterJoin(
            _database.courses,
            _database.courses.code.equalsExp(
              _database.courseOfferings.courseCode,
            ),
          ),
        ]).get();
    final allOfferings = allOfferingRows.map((row) {
      final offering = row.readTable(_database.courseOfferings);
      final course = row.readTableOrNull(_database.courses);
      return (offering: offering, course: course);
    }).toList();
    final data = _buildCourseTableData(rows, allOfferings);

    if (data.scheduled.isEmpty && data.unscheduled.isEmpty) {
      try {
        await refreshCourseTable(semesterId: semesterId);
      } catch (_) {
        // Absorb: yield empty below so UI exits loading state
      }
    }

    yield data;

    final semesterRow = await (_database.select(
      _database.semesters,
    )..where((s) => s.id.equals(semesterId))).getSingleOrNull();
    if (semesterRow == null) return;

    final age = switch (semesterRow.courseTableFetchedAt) {
      final t? => DateTime.now().difference(t),
      null => ttl,
    };

    if (age >= ttl) {
      try {
        await refreshCourseTable(semesterId: semesterId);
      } catch (_) {
        // Absorb: stale data is shown via stream
      }
    }
  }
}