diff --git a/openspec/changes/refactor-dto-and-companion-value-wrapping/.openspec.yaml b/openspec/changes/refactor-dto-and-companion-value-wrapping/.openspec.yaml new file mode 100644 index 000000000..4ea27766c --- /dev/null +++ b/openspec/changes/refactor-dto-and-companion-value-wrapping/.openspec.yaml @@ -0,0 +1,3 @@ +schema: spec-driven +created: 2026-08-19 +skip_specs: true diff --git a/openspec/changes/refactor-dto-and-companion-value-wrapping/design.md b/openspec/changes/refactor-dto-and-companion-value-wrapping/design.md new file mode 100644 index 000000000..85adcce70 --- /dev/null +++ b/openspec/changes/refactor-dto-and-companion-value-wrapping/design.md @@ -0,0 +1,36 @@ +## Context + +See [proposal.md](proposal.md) for the motivation of this change. + +1. `CourseDto.fromJson` assigns both `totalContents` and `totalLessons` from `'contents_count'`. `totalDuration` is also a deprecated field. +2. In `CoursesTable` (`packages/core/lib/data/db/tables/courses_table.dart`), both `totalDuration` and `totalLessons` exist as columns. Since the app has not been released yet, we can safely delete these columns from the database without a schema migration. +3. In `CourseRepository` (`packages/courses/lib/repositories/course_repository.dart`), `_lessonDtoToCompanion` method uses verbose conditional expressions `val != null ? Value(val) : const Value.absent()` for over 30 nullable fields. + +## Goals / Non-Goals + +**Goals:** +- Completely remove `totalDuration` and `totalLessons` from `CourseDto` and `CoursesTable`. +- Update UI references in `course_card.dart` and `info_page.dart` to use `totalContents`. +- Regenerate generated drift file `app_database.g.dart` to apply the database schema changes. +- Refactor `_lessonDtoToCompanion` to use `Value.absentIfNull` for all nullable fields to simplify mapping logic. + +**Non-Goals:** +- Database schema migration scripts (since this is pre-release code). + +## Decisions + +### 1. Completely remove `totalDuration` and `totalLessons` columns and fields +We will: +- Delete `totalDuration` and `totalLessons` columns from `CoursesTable` in `courses_table.dart`. +- Delete `totalDuration` and `totalLessons` fields from `CourseDto` in `course_dto.dart`. +- Transition all UI files (`course_card.dart` and `info_page.dart`) and repository mapping helpers to use `totalContents`. +- Regenerate the database files using `dart run build_runner build --delete-conflicting-outputs` in the core package. + +### 2. Refactor `_lessonDtoToCompanion` to use `Value.absentIfNull` +For all nullable fields mapped from `LessonDto` to `LessonsTableCompanion` in `packages/courses/lib/repositories/course_repository.dart`, we will replace `dto.field != null ? Value(dto.field) : const Value.absent()` with `Value.absentIfNull(dto.field)`. + +## Risks / Trade-offs + +- **Risk:** Build runner fails or other packages depend on the removed columns. + - **Mitigation:** Run `flutter analyze` on the entire monorepo after code generation to resolve any remaining references to `totalLessons` or `totalDuration`. + diff --git a/openspec/changes/refactor-dto-and-companion-value-wrapping/proposal.md b/openspec/changes/refactor-dto-and-companion-value-wrapping/proposal.md new file mode 100644 index 000000000..489510efc --- /dev/null +++ b/openspec/changes/refactor-dto-and-companion-value-wrapping/proposal.md @@ -0,0 +1,32 @@ +## Why + +1. `CourseDto.fromJson` contains a dead assignment where both `totalContents` and `totalLessons` are parsed from `json['contents_count']`. Because `totalLessons` is deprecated and redundant with `totalContents`, it should be completely removed, alongside the old deprecated `totalDuration` field. +2. The app is not released yet, so we can clean up the database columns `totalDuration` and `totalLessons` in `CoursesTable` directly without needing schema migrations. +3. `_lessonDtoToCompanion` in `CourseRepository` contains over 30 lines of verbose conditional checks for nullable fields (`dto.field != null ? Value(dto.field) : const Value.absent()`). These can be simplified using Drift's native `Value.absentIfNull` constructor to improve readability. + +## What Changes + +- Remove `totalDuration` and `totalLessons` fields completely from `CourseDto`. +- Remove `totalDuration` and `totalLessons` columns from `CoursesTable` in `courses_table.dart`, and regenerate drift schema. +- Update repository mapping functions (`rowToCourseDto` and `_courseDtoToCompanion`) to omit removed fields. +- Refactor the 30+ field mappings in `_lessonDtoToCompanion` to use `Value.absentIfNull`. + +## Capabilities + +### New Capabilities + + +### Modified Capabilities + + +> **Note:** This is a pure refactor / code cleanup. No spec-level behaviour changes. +> `skip_specs: true` has been set in `.openspec.yaml`. + +## Impact + +- **Files:** + - `packages/core/lib/data/models/course_dto.dart` + - `packages/core/lib/data/db/tables/courses_table.dart` + - `packages/courses/lib/repositories/course_repository.dart` +- **Scope:** Cleanups of JSON deserialization, database schema, and DB companion mapping. +- **Risk:** Medium (requires regenerating code and checking all usages of these fields/columns). diff --git a/openspec/changes/refactor-dto-and-companion-value-wrapping/tasks.md b/openspec/changes/refactor-dto-and-companion-value-wrapping/tasks.md new file mode 100644 index 000000000..305d90f31 --- /dev/null +++ b/openspec/changes/refactor-dto-and-companion-value-wrapping/tasks.md @@ -0,0 +1,18 @@ +## 1. DTO and Database Column Removal + +- [x] 1.1 Delete `totalDuration` and `totalLessons` fields from `CourseDto` (`packages/core/lib/data/models/course_dto.dart`) +- [x] 1.2 Delete `totalDuration` and `totalLessons` columns from `CoursesTable` (`packages/core/lib/data/db/tables/courses_table.dart`) +- [x] 1.3 Update repository mapping helpers (`rowToCourseDto` and `_courseDtoToCompanion` in `course_repository.dart`) to completely omit these removed columns and DTO fields +- [x] 1.4 Run drift code generation (`dart run build_runner build --delete-conflicting-outputs` inside `packages/core`) to regenerate `app_database.g.dart` +- [x] 1.5 Fix `totalLessons` usages and compiler errors in `packages/exams` +- [x] 1.6 Fix `totalDuration` usages and dynamic list return type compiler error in `packages/profile` + +## 2. Refactor Companion Mappings + +- [x] 2.1 Refactor the 30+ nullable mapping fields in `_lessonDtoToCompanion` inside `packages/courses/lib/repositories/course_repository.dart` to use `Value.absentIfNull` + +## 3. Verification + +- [x] 3.1 Run static analysis across `packages/courses` and `packages/core` to ensure compilation is clean and all deprecated field usages are resolved +- [x] 3.2 Run test suite in `packages/courses` to verify no regressions are introduced + diff --git a/packages/core/lib/data/db/app_database.g.dart b/packages/core/lib/data/db/app_database.g.dart index fb418e137..ebd8c29de 100644 --- a/packages/core/lib/data/db/app_database.g.dart +++ b/packages/core/lib/data/db/app_database.g.dart @@ -49,17 +49,6 @@ class $CoursesTableTable extends CoursesTable type: DriftSqlType.int, requiredDuringInsert: true, ); - static const VerificationMeta _totalDurationMeta = const VerificationMeta( - 'totalDuration', - ); - @override - late final GeneratedColumn totalDuration = GeneratedColumn( - 'total_duration', - aliasedName, - true, - type: DriftSqlType.string, - requiredDuringInsert: false, - ); static const VerificationMeta _totalContentsMeta = const VerificationMeta( 'totalContents', ); @@ -96,17 +85,6 @@ class $CoursesTableTable extends CoursesTable requiredDuringInsert: false, defaultValue: const Constant(0), ); - static const VerificationMeta _totalLessonsMeta = const VerificationMeta( - 'totalLessons', - ); - @override - late final GeneratedColumn totalLessons = GeneratedColumn( - 'total_lessons', - aliasedName, - false, - type: DriftSqlType.int, - requiredDuringInsert: true, - ); static const VerificationMeta _imageMeta = const VerificationMeta('image'); @override late final GeneratedColumn image = GeneratedColumn( @@ -181,11 +159,9 @@ class $CoursesTableTable extends CoursesTable title, colorIndex, chapterCount, - totalDuration, totalContents, progress, completedLessons, - totalLessons, image, tags, allowedDevices, @@ -237,15 +213,6 @@ class $CoursesTableTable extends CoursesTable } else if (isInserting) { context.missing(_chapterCountMeta); } - if (data.containsKey('total_duration')) { - context.handle( - _totalDurationMeta, - totalDuration.isAcceptableOrUnknown( - data['total_duration']!, - _totalDurationMeta, - ), - ); - } if (data.containsKey('total_contents')) { context.handle( _totalContentsMeta, @@ -270,17 +237,6 @@ class $CoursesTableTable extends CoursesTable ), ); } - if (data.containsKey('total_lessons')) { - context.handle( - _totalLessonsMeta, - totalLessons.isAcceptableOrUnknown( - data['total_lessons']!, - _totalLessonsMeta, - ), - ); - } else if (isInserting) { - context.missing(_totalLessonsMeta); - } if (data.containsKey('image')) { context.handle( _imageMeta, @@ -348,10 +304,6 @@ class $CoursesTableTable extends CoursesTable DriftSqlType.int, data['${effectivePrefix}chapter_count'], )!, - totalDuration: attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}total_duration'], - ), totalContents: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}total_contents'], @@ -364,10 +316,6 @@ class $CoursesTableTable extends CoursesTable DriftSqlType.int, data['${effectivePrefix}completed_lessons'], )!, - totalLessons: attachedDatabase.typeMapping.read( - DriftSqlType.int, - data['${effectivePrefix}total_lessons'], - )!, image: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}image'], @@ -407,11 +355,9 @@ class CoursesTableData extends DataClass final String title; final int colorIndex; final int chapterCount; - final String? totalDuration; final int totalContents; final double progress; final int completedLessons; - final int totalLessons; final String? image; final String? tags; final String? allowedDevices; @@ -423,11 +369,9 @@ class CoursesTableData extends DataClass required this.title, required this.colorIndex, required this.chapterCount, - this.totalDuration, required this.totalContents, required this.progress, required this.completedLessons, - required this.totalLessons, this.image, this.tags, this.allowedDevices, @@ -442,13 +386,9 @@ class CoursesTableData extends DataClass map['title'] = Variable(title); map['color_index'] = Variable(colorIndex); map['chapter_count'] = Variable(chapterCount); - if (!nullToAbsent || totalDuration != null) { - map['total_duration'] = Variable(totalDuration); - } map['total_contents'] = Variable(totalContents); map['progress'] = Variable(progress); map['completed_lessons'] = Variable(completedLessons); - map['total_lessons'] = Variable(totalLessons); if (!nullToAbsent || image != null) { map['image'] = Variable(image); } @@ -470,13 +410,9 @@ class CoursesTableData extends DataClass title: Value(title), colorIndex: Value(colorIndex), chapterCount: Value(chapterCount), - totalDuration: totalDuration == null && nullToAbsent - ? const Value.absent() - : Value(totalDuration), totalContents: Value(totalContents), progress: Value(progress), completedLessons: Value(completedLessons), - totalLessons: Value(totalLessons), image: image == null && nullToAbsent ? const Value.absent() : Value(image), @@ -500,11 +436,9 @@ class CoursesTableData extends DataClass title: serializer.fromJson(json['title']), colorIndex: serializer.fromJson(json['colorIndex']), chapterCount: serializer.fromJson(json['chapterCount']), - totalDuration: serializer.fromJson(json['totalDuration']), totalContents: serializer.fromJson(json['totalContents']), progress: serializer.fromJson(json['progress']), completedLessons: serializer.fromJson(json['completedLessons']), - totalLessons: serializer.fromJson(json['totalLessons']), image: serializer.fromJson(json['image']), tags: serializer.fromJson(json['tags']), allowedDevices: serializer.fromJson(json['allowedDevices']), @@ -521,11 +455,9 @@ class CoursesTableData extends DataClass 'title': serializer.toJson(title), 'colorIndex': serializer.toJson(colorIndex), 'chapterCount': serializer.toJson(chapterCount), - 'totalDuration': serializer.toJson(totalDuration), 'totalContents': serializer.toJson(totalContents), 'progress': serializer.toJson(progress), 'completedLessons': serializer.toJson(completedLessons), - 'totalLessons': serializer.toJson(totalLessons), 'image': serializer.toJson(image), 'tags': serializer.toJson(tags), 'allowedDevices': serializer.toJson(allowedDevices), @@ -540,11 +472,9 @@ class CoursesTableData extends DataClass String? title, int? colorIndex, int? chapterCount, - Value totalDuration = const Value.absent(), int? totalContents, double? progress, int? completedLessons, - int? totalLessons, Value image = const Value.absent(), Value tags = const Value.absent(), Value allowedDevices = const Value.absent(), @@ -556,13 +486,9 @@ class CoursesTableData extends DataClass title: title ?? this.title, colorIndex: colorIndex ?? this.colorIndex, chapterCount: chapterCount ?? this.chapterCount, - totalDuration: totalDuration.present - ? totalDuration.value - : this.totalDuration, totalContents: totalContents ?? this.totalContents, progress: progress ?? this.progress, completedLessons: completedLessons ?? this.completedLessons, - totalLessons: totalLessons ?? this.totalLessons, image: image.present ? image.value : this.image, tags: tags.present ? tags.value : this.tags, allowedDevices: allowedDevices.present @@ -582,9 +508,6 @@ class CoursesTableData extends DataClass chapterCount: data.chapterCount.present ? data.chapterCount.value : this.chapterCount, - totalDuration: data.totalDuration.present - ? data.totalDuration.value - : this.totalDuration, totalContents: data.totalContents.present ? data.totalContents.value : this.totalContents, @@ -592,9 +515,6 @@ class CoursesTableData extends DataClass completedLessons: data.completedLessons.present ? data.completedLessons.value : this.completedLessons, - totalLessons: data.totalLessons.present - ? data.totalLessons.value - : this.totalLessons, image: data.image.present ? data.image.value : this.image, tags: data.tags.present ? data.tags.value : this.tags, allowedDevices: data.allowedDevices.present @@ -619,11 +539,9 @@ class CoursesTableData extends DataClass ..write('title: $title, ') ..write('colorIndex: $colorIndex, ') ..write('chapterCount: $chapterCount, ') - ..write('totalDuration: $totalDuration, ') ..write('totalContents: $totalContents, ') ..write('progress: $progress, ') ..write('completedLessons: $completedLessons, ') - ..write('totalLessons: $totalLessons, ') ..write('image: $image, ') ..write('tags: $tags, ') ..write('allowedDevices: $allowedDevices, ') @@ -640,11 +558,9 @@ class CoursesTableData extends DataClass title, colorIndex, chapterCount, - totalDuration, totalContents, progress, completedLessons, - totalLessons, image, tags, allowedDevices, @@ -660,11 +576,9 @@ class CoursesTableData extends DataClass other.title == this.title && other.colorIndex == this.colorIndex && other.chapterCount == this.chapterCount && - other.totalDuration == this.totalDuration && other.totalContents == this.totalContents && other.progress == this.progress && other.completedLessons == this.completedLessons && - other.totalLessons == this.totalLessons && other.image == this.image && other.tags == this.tags && other.allowedDevices == this.allowedDevices && @@ -678,11 +592,9 @@ class CoursesTableCompanion extends UpdateCompanion { final Value title; final Value colorIndex; final Value chapterCount; - final Value totalDuration; final Value totalContents; final Value progress; final Value completedLessons; - final Value totalLessons; final Value image; final Value tags; final Value allowedDevices; @@ -695,11 +607,9 @@ class CoursesTableCompanion extends UpdateCompanion { this.title = const Value.absent(), this.colorIndex = const Value.absent(), this.chapterCount = const Value.absent(), - this.totalDuration = const Value.absent(), this.totalContents = const Value.absent(), this.progress = const Value.absent(), this.completedLessons = const Value.absent(), - this.totalLessons = const Value.absent(), this.image = const Value.absent(), this.tags = const Value.absent(), this.allowedDevices = const Value.absent(), @@ -713,11 +623,9 @@ class CoursesTableCompanion extends UpdateCompanion { required String title, required int colorIndex, required int chapterCount, - this.totalDuration = const Value.absent(), this.totalContents = const Value.absent(), this.progress = const Value.absent(), this.completedLessons = const Value.absent(), - required int totalLessons, this.image = const Value.absent(), this.tags = const Value.absent(), this.allowedDevices = const Value.absent(), @@ -728,18 +636,15 @@ class CoursesTableCompanion extends UpdateCompanion { }) : id = Value(id), title = Value(title), colorIndex = Value(colorIndex), - chapterCount = Value(chapterCount), - totalLessons = Value(totalLessons); + chapterCount = Value(chapterCount); static Insertable custom({ Expression? id, Expression? title, Expression? colorIndex, Expression? chapterCount, - Expression? totalDuration, Expression? totalContents, Expression? progress, Expression? completedLessons, - Expression? totalLessons, Expression? image, Expression? tags, Expression? allowedDevices, @@ -753,11 +658,9 @@ class CoursesTableCompanion extends UpdateCompanion { if (title != null) 'title': title, if (colorIndex != null) 'color_index': colorIndex, if (chapterCount != null) 'chapter_count': chapterCount, - if (totalDuration != null) 'total_duration': totalDuration, if (totalContents != null) 'total_contents': totalContents, if (progress != null) 'progress': progress, if (completedLessons != null) 'completed_lessons': completedLessons, - if (totalLessons != null) 'total_lessons': totalLessons, if (image != null) 'image': image, if (tags != null) 'tags': tags, if (allowedDevices != null) 'allowed_devices': allowedDevices, @@ -773,11 +676,9 @@ class CoursesTableCompanion extends UpdateCompanion { Value? title, Value? colorIndex, Value? chapterCount, - Value? totalDuration, Value? totalContents, Value? progress, Value? completedLessons, - Value? totalLessons, Value? image, Value? tags, Value? allowedDevices, @@ -791,11 +692,9 @@ class CoursesTableCompanion extends UpdateCompanion { title: title ?? this.title, colorIndex: colorIndex ?? this.colorIndex, chapterCount: chapterCount ?? this.chapterCount, - totalDuration: totalDuration ?? this.totalDuration, totalContents: totalContents ?? this.totalContents, progress: progress ?? this.progress, completedLessons: completedLessons ?? this.completedLessons, - totalLessons: totalLessons ?? this.totalLessons, image: image ?? this.image, tags: tags ?? this.tags, allowedDevices: allowedDevices ?? this.allowedDevices, @@ -821,9 +720,6 @@ class CoursesTableCompanion extends UpdateCompanion { if (chapterCount.present) { map['chapter_count'] = Variable(chapterCount.value); } - if (totalDuration.present) { - map['total_duration'] = Variable(totalDuration.value); - } if (totalContents.present) { map['total_contents'] = Variable(totalContents.value); } @@ -833,9 +729,6 @@ class CoursesTableCompanion extends UpdateCompanion { if (completedLessons.present) { map['completed_lessons'] = Variable(completedLessons.value); } - if (totalLessons.present) { - map['total_lessons'] = Variable(totalLessons.value); - } if (image.present) { map['image'] = Variable(image.value); } @@ -867,11 +760,9 @@ class CoursesTableCompanion extends UpdateCompanion { ..write('title: $title, ') ..write('colorIndex: $colorIndex, ') ..write('chapterCount: $chapterCount, ') - ..write('totalDuration: $totalDuration, ') ..write('totalContents: $totalContents, ') ..write('progress: $progress, ') ..write('completedLessons: $completedLessons, ') - ..write('totalLessons: $totalLessons, ') ..write('image: $image, ') ..write('tags: $tags, ') ..write('allowedDevices: $allowedDevices, ') @@ -17291,11 +17182,9 @@ typedef $$CoursesTableTableCreateCompanionBuilder = required String title, required int colorIndex, required int chapterCount, - Value totalDuration, Value totalContents, Value progress, Value completedLessons, - required int totalLessons, Value image, Value tags, Value allowedDevices, @@ -17310,11 +17199,9 @@ typedef $$CoursesTableTableUpdateCompanionBuilder = Value title, Value colorIndex, Value chapterCount, - Value totalDuration, Value totalContents, Value progress, Value completedLessons, - Value totalLessons, Value image, Value tags, Value allowedDevices, @@ -17353,11 +17240,6 @@ class $$CoursesTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get totalDuration => $composableBuilder( - column: $table.totalDuration, - builder: (column) => ColumnFilters(column), - ); - ColumnFilters get totalContents => $composableBuilder( column: $table.totalContents, builder: (column) => ColumnFilters(column), @@ -17373,11 +17255,6 @@ class $$CoursesTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get totalLessons => $composableBuilder( - column: $table.totalLessons, - builder: (column) => ColumnFilters(column), - ); - ColumnFilters get image => $composableBuilder( column: $table.image, builder: (column) => ColumnFilters(column), @@ -17438,11 +17315,6 @@ class $$CoursesTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get totalDuration => $composableBuilder( - column: $table.totalDuration, - builder: (column) => ColumnOrderings(column), - ); - ColumnOrderings get totalContents => $composableBuilder( column: $table.totalContents, builder: (column) => ColumnOrderings(column), @@ -17458,11 +17330,6 @@ class $$CoursesTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get totalLessons => $composableBuilder( - column: $table.totalLessons, - builder: (column) => ColumnOrderings(column), - ); - ColumnOrderings get image => $composableBuilder( column: $table.image, builder: (column) => ColumnOrderings(column), @@ -17519,11 +17386,6 @@ class $$CoursesTableTableAnnotationComposer builder: (column) => column, ); - GeneratedColumn get totalDuration => $composableBuilder( - column: $table.totalDuration, - builder: (column) => column, - ); - GeneratedColumn get totalContents => $composableBuilder( column: $table.totalContents, builder: (column) => column, @@ -17537,11 +17399,6 @@ class $$CoursesTableTableAnnotationComposer builder: (column) => column, ); - GeneratedColumn get totalLessons => $composableBuilder( - column: $table.totalLessons, - builder: (column) => column, - ); - GeneratedColumn get image => $composableBuilder(column: $table.image, builder: (column) => column); @@ -17604,11 +17461,9 @@ class $$CoursesTableTableTableManager Value title = const Value.absent(), Value colorIndex = const Value.absent(), Value chapterCount = const Value.absent(), - Value totalDuration = const Value.absent(), Value totalContents = const Value.absent(), Value progress = const Value.absent(), Value completedLessons = const Value.absent(), - Value totalLessons = const Value.absent(), Value image = const Value.absent(), Value tags = const Value.absent(), Value allowedDevices = const Value.absent(), @@ -17621,11 +17476,9 @@ class $$CoursesTableTableTableManager title: title, colorIndex: colorIndex, chapterCount: chapterCount, - totalDuration: totalDuration, totalContents: totalContents, progress: progress, completedLessons: completedLessons, - totalLessons: totalLessons, image: image, tags: tags, allowedDevices: allowedDevices, @@ -17640,11 +17493,9 @@ class $$CoursesTableTableTableManager required String title, required int colorIndex, required int chapterCount, - Value totalDuration = const Value.absent(), Value totalContents = const Value.absent(), Value progress = const Value.absent(), Value completedLessons = const Value.absent(), - required int totalLessons, Value image = const Value.absent(), Value tags = const Value.absent(), Value allowedDevices = const Value.absent(), @@ -17657,11 +17508,9 @@ class $$CoursesTableTableTableManager title: title, colorIndex: colorIndex, chapterCount: chapterCount, - totalDuration: totalDuration, totalContents: totalContents, progress: progress, completedLessons: completedLessons, - totalLessons: totalLessons, image: image, tags: tags, allowedDevices: allowedDevices, diff --git a/packages/core/lib/data/db/tables/courses_table.dart b/packages/core/lib/data/db/tables/courses_table.dart index 414478af4..be07d2ece 100644 --- a/packages/core/lib/data/db/tables/courses_table.dart +++ b/packages/core/lib/data/db/tables/courses_table.dart @@ -6,11 +6,9 @@ class CoursesTable extends Table { TextColumn get title => text()(); IntColumn get colorIndex => integer()(); IntColumn get chapterCount => integer()(); - TextColumn get totalDuration => text().nullable()(); IntColumn get totalContents => integer().withDefault(const Constant(0))(); RealColumn get progress => real().withDefault(const Constant(0.0))(); IntColumn get completedLessons => integer().withDefault(const Constant(0))(); - IntColumn get totalLessons => integer()(); TextColumn get image => text().nullable()(); TextColumn get tags => text().nullable()(); TextColumn get allowedDevices => text().nullable()(); diff --git a/packages/core/lib/data/models/course_dto.dart b/packages/core/lib/data/models/course_dto.dart index 7aaa130e2..97d69d8e0 100644 --- a/packages/core/lib/data/models/course_dto.dart +++ b/packages/core/lib/data/models/course_dto.dart @@ -15,12 +15,9 @@ class CourseDto { final int colorIndex; final int chapterCount; - @Deprecated('Use totalContents instead') - final String? totalDuration; final int totalContents; final double progress; // 0.0–100.0 final int completedLessons; - final int totalLessons; final String? image; final List tags; final List allowedDevices; @@ -33,11 +30,9 @@ class CourseDto { required this.title, required this.colorIndex, required this.chapterCount, - this.totalDuration, required this.totalContents, required this.progress, required this.completedLessons, - required this.totalLessons, this.tags = const [], this.allowedDevices = const [], this.examsCount = 0, @@ -64,11 +59,9 @@ class CourseDto { String? title, int? colorIndex, int? chapterCount, - String? totalDuration, int? totalContents, double? progress, int? completedLessons, - int? totalLessons, String? image, List? tags, List? allowedDevices, @@ -82,11 +75,9 @@ class CourseDto { title: title ?? this.title, colorIndex: colorIndex ?? this.colorIndex, chapterCount: chapterCount ?? this.chapterCount, - totalDuration: totalDuration ?? this.totalDuration, totalContents: totalContents ?? this.totalContents, progress: progress ?? this.progress, completedLessons: completedLessons ?? this.completedLessons, - totalLessons: totalLessons ?? this.totalLessons, image: image ?? this.image, tags: tags ?? this.tags, allowedDevices: allowedDevices ?? this.allowedDevices, @@ -103,11 +94,9 @@ class CourseDto { title: json['title'] as String? ?? 'Untitled Course', colorIndex: json['color_index'] as int? ?? 0, chapterCount: json['chapters_count'] as int? ?? 0, - totalDuration: json['total_duration'] as String?, totalContents: json['contents_count'] as int? ?? 0, progress: (json['progress'] as num? ?? 0.0).toDouble(), completedLessons: json['completed_lessons_count'] as int? ?? 0, - totalLessons: json['contents_count'] as int? ?? 0, image: json['image'] as String?, tags: _parseList(json['tags']), allowedDevices: _parseList(json['allowed_devices']), @@ -177,11 +166,9 @@ class CourseDto { 'title': title, 'colorIndex': colorIndex, 'chapterCount': chapterCount, - 'totalDuration': totalDuration, 'totalContents': totalContents, 'progress': progress, 'completedLessons': completedLessons, - 'totalLessons': totalLessons, 'image': image, 'tags': tags, 'allowed_devices': allowedDevices, diff --git a/packages/core/lib/data/sources/mock_data_source.dart b/packages/core/lib/data/sources/mock_data_source.dart index 817ae6615..752481eeb 100644 --- a/packages/core/lib/data/sources/mock_data_source.dart +++ b/packages/core/lib/data/sources/mock_data_source.dart @@ -89,55 +89,45 @@ class MockDataSource implements DataSource { title: 'JEE Main 2026', colorIndex: 0, // indigo chapterCount: 12, - totalDuration: '180 hrs', totalContents: 120, progress: 34.0, completedLessons: 28, - totalLessons: 84, ), const CourseDto( id: 'neet-2026', title: 'NEET 2026', colorIndex: 4, // rose chapterCount: 10, - totalDuration: '160 hrs', totalContents: 110, progress: 18.0, completedLessons: 14, - totalLessons: 76, ), const CourseDto( id: 'jee-advanced-2026', title: 'JEE Advanced 2026', colorIndex: 3, // violet chapterCount: 8, - totalDuration: '120 hrs', totalContents: 80, progress: 5.0, completedLessons: 3, - totalLessons: 60, ), const CourseDto( id: 'biology-neet-2026', title: 'NEET Biology Mastery', colorIndex: 2, // emerald chapterCount: 15, - totalDuration: '200 hrs', totalContents: 150, progress: 45.0, completedLessons: 45, - totalLessons: 100, ), const CourseDto( id: 'english-core-2026', title: 'CBSE English Core', colorIndex: 5, // pink chapterCount: 6, - totalDuration: '40 hrs', totalContents: 40, progress: 10.0, completedLessons: 2, - totalLessons: 20, ), ]; } else if (page == 2) { @@ -147,22 +137,18 @@ class MockDataSource implements DataSource { title: 'Maths Foundation 2025', colorIndex: 1, chapterCount: 15, - totalDuration: '100 hrs', totalContents: 90, progress: 0.0, completedLessons: 0, - totalLessons: 50, ), const CourseDto( id: 'physics-mastery', title: 'Physics Mastery 2025', colorIndex: 6, chapterCount: 20, - totalDuration: '150 hrs', totalContents: 130, progress: 12.0, completedLessons: 10, - totalLessons: 80, ), ]; } else if (page == 3) { @@ -172,11 +158,9 @@ class MockDataSource implements DataSource { title: 'Chemistry Quick Revision', colorIndex: 7, chapterCount: 5, - totalDuration: '20 hrs', totalContents: 25, progress: 100.0, completedLessons: 20, - totalLessons: 20, ), ]; } diff --git a/packages/courses/lib/data/mock_courses.dart b/packages/courses/lib/data/mock_courses.dart index 764b2b7ac..859d6bb5f 100644 --- a/packages/courses/lib/data/mock_courses.dart +++ b/packages/courses/lib/data/mock_courses.dart @@ -11,7 +11,6 @@ const mockCourses = [ chapterCount: 5, totalContents: 20, completedLessons: 13, - totalLessons: 20, progress: 65.0, ), CourseDto( @@ -21,7 +20,6 @@ const mockCourses = [ chapterCount: 4, totalContents: 15, completedLessons: 5, - totalLessons: 15, progress: 30.0, ), CourseDto( @@ -31,7 +29,6 @@ const mockCourses = [ chapterCount: 3, totalContents: 10, completedLessons: 0, - totalLessons: 10, progress: 0.0, ), CourseDto( @@ -41,7 +38,6 @@ const mockCourses = [ chapterCount: 6, totalContents: 25, completedLessons: 21, - totalLessons: 25, progress: 85.0, ), CourseDto( @@ -51,7 +47,6 @@ const mockCourses = [ chapterCount: 3, totalContents: 12, completedLessons: 2, - totalLessons: 12, progress: 15.0, ), CourseDto( @@ -61,7 +56,6 @@ const mockCourses = [ chapterCount: 2, totalContents: 8, completedLessons: 0, - totalLessons: 8, progress: 0.0, ), ]; diff --git a/packages/courses/lib/repositories/course_repository.dart b/packages/courses/lib/repositories/course_repository.dart index fb1422eda..f1ce88e55 100644 --- a/packages/courses/lib/repositories/course_repository.dart +++ b/packages/courses/lib/repositories/course_repository.dart @@ -1142,7 +1142,6 @@ class CourseRepository { totalContents: row.totalContents, progress: row.progress, completedLessons: row.completedLessons, - totalLessons: row.totalLessons, image: row.image, tags: _safeDecodeList(row.tags), allowedDevices: _safeDecodeList(row.allowedDevices), @@ -1169,7 +1168,6 @@ class CourseRepository { totalContents: Value(dto.totalContents), progress: Value(dto.progress), completedLessons: Value(dto.completedLessons), - totalLessons: Value(dto.totalLessons), image: dto.image != null ? Value(dto.image) : const Value.absent(), tags: dto.tags.isNotEmpty ? Value(jsonEncode(dto.tags)) @@ -1282,108 +1280,59 @@ class CourseRepository { LessonsTableCompanion( id: Value(dto.id), chapterId: Value(dto.chapterId), - courseId: - dto.courseId != null ? Value(dto.courseId) : const Value.absent(), - ancestorChapterIds: dto.ancestorChapterIds != null - ? Value(dto.ancestorChapterIds) - : const Value.absent(), + courseId: Value.absentIfNull(dto.courseId), + ancestorChapterIds: Value.absentIfNull(dto.ancestorChapterIds), title: Value(dto.title), type: Value(dto.type.name), duration: Value(dto.duration), progressStatus: Value(dto.progressStatus.name), isLocked: Value(dto.isLocked), orderIndex: Value(dto.orderIndex), - chapterTitle: dto.chapterTitle != null - ? Value(dto.chapterTitle) - : const Value.absent(), - uuid: dto.uuid != null ? Value(dto.uuid) : const Value.absent(), - contentUrl: dto.contentUrl != null - ? Value(dto.contentUrl) - : const Value.absent(), - subtitle: - dto.subtitle != null ? Value(dto.subtitle) : const Value.absent(), - subjectName: dto.subjectName != null - ? Value(dto.subjectName) - : const Value.absent(), - subjectIndex: dto.subjectIndex != null - ? Value(dto.subjectIndex) - : const Value.absent(), - lessonNumber: dto.lessonNumber != null - ? Value(dto.lessonNumber) - : const Value.absent(), - totalLessons: dto.totalLessons != null - ? Value(dto.totalLessons) - : const Value.absent(), + chapterTitle: Value.absentIfNull(dto.chapterTitle), + uuid: Value.absentIfNull(dto.uuid), + contentUrl: Value.absentIfNull(dto.contentUrl), + subtitle: Value.absentIfNull(dto.subtitle), + subjectName: Value.absentIfNull(dto.subjectName), + subjectIndex: Value.absentIfNull(dto.subjectIndex), + lessonNumber: Value.absentIfNull(dto.lessonNumber), + totalLessons: Value.absentIfNull(dto.totalLessons), bookmarkId: Value(dto.bookmarkId), isRunning: Value(dto.isRunning), isUpcoming: Value(dto.isUpcoming), hasAttempts: Value(dto.hasAttempts), - image: dto.image != null ? Value(dto.image) : const Value.absent(), - start: dto.start != null ? Value(dto.start) : const Value.absent(), - end: dto.end != null ? Value(dto.end) : const Value.absent(), + image: Value.absentIfNull(dto.image), + start: Value.absentIfNull(dto.start), + end: Value.absentIfNull(dto.end), hasEnded: Value(dto.hasEnded), - nextContentId: dto.nextContentId != null - ? Value(dto.nextContentId) - : const Value.absent(), - previousContentId: dto.previousContentId != null - ? Value(dto.previousContentId) - : const Value.absent(), - htmlContent: dto.htmlContent != null - ? Value(dto.htmlContent) - : const Value.absent(), + nextContentId: Value.absentIfNull(dto.nextContentId), + previousContentId: Value.absentIfNull(dto.previousContentId), + htmlContent: Value.absentIfNull(dto.htmlContent), isDetailFetched: Value(dto.isDetailFetched), - chatEmbedUrl: dto.chatEmbedUrl != null - ? Value(dto.chatEmbedUrl) - : const Value.absent(), - streamStatus: dto.streamStatus != null - ? Value(dto.streamStatus) - : const Value.absent(), + chatEmbedUrl: Value.absentIfNull(dto.chatEmbedUrl), + streamStatus: Value.absentIfNull(dto.streamStatus), showRecordedVideo: Value(dto.showRecordedVideo), - liveStreamProvider: dto.liveStreamProvider != null - ? Value(dto.liveStreamProvider) - : const Value.absent(), - conferenceId: dto.conferenceId != null - ? Value(dto.conferenceId) - : const Value.absent(), - password: - dto.password != null ? Value(dto.password) : const Value.absent(), - accessToken: dto.accessToken != null - ? Value(dto.accessToken) - : const Value.absent(), + liveStreamProvider: Value.absentIfNull(dto.liveStreamProvider), + conferenceId: Value.absentIfNull(dto.conferenceId), + password: Value.absentIfNull(dto.password), + accessToken: Value.absentIfNull(dto.accessToken), isScheduled: Value(dto.isScheduled), - scheduledMessage: dto.scheduledMessage != null - ? Value(dto.scheduledMessage) - : const Value.absent(), - attemptsUrl: dto.attemptsUrl != null - ? Value(dto.attemptsUrl) - : const Value.absent(), - slug: dto.slug != null ? Value(dto.slug) : const Value.absent(), - description: dto.description != null - ? Value(dto.description) - : const Value.absent(), + scheduledMessage: Value.absentIfNull(dto.scheduledMessage), + attemptsUrl: Value.absentIfNull(dto.attemptsUrl), + slug: Value.absentIfNull(dto.slug), + description: Value.absentIfNull(dto.description), enableTranscript: Value(dto.enableTranscript), - videoSubtitleUrl: dto.videoSubtitleUrl != null - ? Value(dto.videoSubtitleUrl) - : const Value.absent(), + videoSubtitleUrl: Value.absentIfNull(dto.videoSubtitleUrl), isAiEnabled: Value(dto.isAiEnabled), canEnableLearnlensAi: Value(dto.canEnableLearnlensAi), - learnlensAssetId: dto.learnlensAssetId != null - ? Value(dto.learnlensAssetId) - : const Value.absent(), - learnlensAssetStatus: dto.learnlensAssetStatus != null - ? Value(dto.learnlensAssetStatus) - : const Value.absent(), - aiNotesUrl: dto.aiNotesUrl != null - ? Value(dto.aiNotesUrl) - : const Value.absent(), - lastWatchedDuration: dto.lastWatchedDuration != null - ? Value(dto.lastWatchedDuration) - : const Value.absent(), + learnlensAssetId: Value.absentIfNull(dto.learnlensAssetId), + learnlensAssetStatus: Value.absentIfNull(dto.learnlensAssetStatus), + aiNotesUrl: Value.absentIfNull(dto.aiNotesUrl), + lastWatchedDuration: Value.absentIfNull(dto.lastWatchedDuration), allowDownload: Value(dto.allowDownload), watermarkBeforeDownload: Value(dto.watermarkBeforeDownload), - examMetadataJson: dto.exam != null - ? Value(jsonEncode(dto.exam!.toJson())) - : const Value.absent(), + examMetadataJson: Value.absentIfNull( + dto.exam == null ? null : jsonEncode(dto.exam!.toJson()), + ), ); LessonType _parseType(String s) { diff --git a/packages/courses/lib/screens/info/info_page.dart b/packages/courses/lib/screens/info/info_page.dart index 06e95fb6b..0a6d8541a 100644 --- a/packages/courses/lib/screens/info/info_page.dart +++ b/packages/courses/lib/screens/info/info_page.dart @@ -391,7 +391,7 @@ class _InfoCourseCard extends StatelessWidget { ), const SizedBox(width: 4), AppText.cardCaption( - l10n.infoPageLessonsCount(course.totalLessons), + l10n.infoPageLessonsCount(course.totalContents), color: design.colors.textSecondary, ), ], @@ -421,7 +421,6 @@ final _mockSkeletonCourses = List.generate( totalContents: 20, progress: 0, completedLessons: 0, - totalLessons: 12, examsCount: 0, tags: const ['Info'], ), diff --git a/packages/courses/lib/widgets/course_card.dart b/packages/courses/lib/widgets/course_card.dart index 43dd7e2d4..aab75b49e 100644 --- a/packages/courses/lib/widgets/course_card.dart +++ b/packages/courses/lib/widgets/course_card.dart @@ -109,7 +109,7 @@ class CourseCard extends StatelessWidget { children: [ _ProgressStat( value: - '${course.completedLessons}/${course.totalLessons}', + '${course.completedLessons}/${course.totalContents}', label: L10n.of(context).labelLessonsPlural, ), _ProgressStat( diff --git a/packages/courses/lib/widgets/study_content_list.dart b/packages/courses/lib/widgets/study_content_list.dart index 3cc3f55b2..de5f4faae 100644 --- a/packages/courses/lib/widgets/study_content_list.dart +++ b/packages/courses/lib/widgets/study_content_list.dart @@ -195,7 +195,6 @@ final _skeletonCourses = List.generate( totalContents: 48, progress: 0, completedLessons: 0, - totalLessons: 48, image: '', examsCount: 0, order: index, diff --git a/packages/courses/test/widgets/chapters_list_page_test.dart b/packages/courses/test/widgets/chapters_list_page_test.dart index 3c1302407..71cae282d 100644 --- a/packages/courses/test/widgets/chapters_list_page_test.dart +++ b/packages/courses/test/widgets/chapters_list_page_test.dart @@ -11,11 +11,9 @@ void main() { title: 'Test Course', colorIndex: 0, chapterCount: 2, - totalDuration: '2h', totalContents: 10, progress: 0, completedLessons: 0, - totalLessons: 10, ); final testChapters = [ diff --git a/packages/courses/test/widgets/course_card_test.dart b/packages/courses/test/widgets/course_card_test.dart index c9be82938..d9c3390bb 100644 --- a/packages/courses/test/widgets/course_card_test.dart +++ b/packages/courses/test/widgets/course_card_test.dart @@ -33,11 +33,9 @@ void main() { title: 'Flutter Basics', colorIndex: 0, chapterCount: 5, - totalDuration: '10h', totalContents: 50, progress: 65, completedLessons: 65, - totalLessons: 100, ); testWidgets('course title is accessible', (tester) async { @@ -88,11 +86,9 @@ void main() { title: 'Advanced Flutter', colorIndex: 0, chapterCount: 3, - totalDuration: '6h', totalContents: 30, progress: 0, completedLessons: 0, - totalLessons: 50, ); await tester.pumpWidget(wrap(CourseCard(course: notStartedCourse))); diff --git a/packages/exams/lib/screens/custom_exam_course_selection_screen.dart b/packages/exams/lib/screens/custom_exam_course_selection_screen.dart index a04f356b6..f3e925599 100644 --- a/packages/exams/lib/screens/custom_exam_course_selection_screen.dart +++ b/packages/exams/lib/screens/custom_exam_course_selection_screen.dart @@ -140,7 +140,6 @@ class _CustomExamCourseSelectionScreenState totalContents: 0, progress: 0, completedLessons: 0, - totalLessons: 10, ), ); } else if (coursesAsyncValue.hasError) { @@ -279,7 +278,7 @@ class _CustomExamCourseSelectionScreenState ), const SizedBox(height: 2), AppText.bodySmall( - '${course.totalLessons} Lessons', + '${course.totalContents} Lessons', color: design.colors.textSecondary, ), ], diff --git a/packages/exams/lib/screens/exams_screen.dart b/packages/exams/lib/screens/exams_screen.dart index a4e4f261a..668333ddd 100644 --- a/packages/exams/lib/screens/exams_screen.dart +++ b/packages/exams/lib/screens/exams_screen.dart @@ -228,7 +228,6 @@ final _skeletonCourses = List.generate( totalContents: 48, progress: 0, completedLessons: 0, - totalLessons: 48, image: '', examsCount: 0, order: index, diff --git a/packages/profile/lib/providers/certificates_provider.dart b/packages/profile/lib/providers/certificates_provider.dart index 705146068..0c3101052 100644 --- a/packages/profile/lib/providers/certificates_provider.dart +++ b/packages/profile/lib/providers/certificates_provider.dart @@ -15,11 +15,9 @@ final List _paidActiveCertificates = [ title: 'JEE Main Physics - Complete Course', colorIndex: 3, chapterCount: 24, - totalDuration: '48h', totalContents: 140, progress: 100, completedLessons: 84, - totalLessons: 84, ), studentName: 'Rahul Sharma', completionDate: DateTime(2025, 12, 15), @@ -40,11 +38,9 @@ final List _paidActiveCertificates = [ title: 'Organic Chemistry Fundamentals', colorIndex: 1, chapterCount: 18, - totalDuration: '32h', totalContents: 100, progress: 45, completedLessons: 27, - totalLessons: 60, ), studentName: 'Rahul Sharma', progress: 45, @@ -57,11 +53,9 @@ final List _paidActiveCertificates = [ title: 'Advanced Calculus for JEE', colorIndex: 2, chapterCount: 21, - totalDuration: '36h', totalContents: 120, progress: 28, completedLessons: 14, - totalLessons: 50, ), studentName: 'Rahul Sharma', progress: 28, @@ -74,11 +68,9 @@ final List _paidActiveCertificates = [ title: 'NEET Biology - Cell Biology & Genetics', colorIndex: 3, chapterCount: 16, - totalDuration: '30h', totalContents: 90, progress: 12, completedLessons: 8, - totalLessons: 66, ), studentName: 'Rahul Sharma', progress: 12, diff --git a/packages/profile/lib/providers/profile_providers.dart b/packages/profile/lib/providers/profile_providers.dart index 5f4c2f4dd..0b36214cf 100644 --- a/packages/profile/lib/providers/profile_providers.dart +++ b/packages/profile/lib/providers/profile_providers.dart @@ -16,11 +16,9 @@ Stream> profileEnrollment(Ref ref) async* { title: row.title, colorIndex: row.colorIndex, chapterCount: row.chapterCount, - totalDuration: row.totalDuration, totalContents: row.totalContents, progress: row.progress, completedLessons: row.completedLessons, - totalLessons: row.totalLessons, ), ) .toList(), diff --git a/packages/profile/lib/providers/profile_providers.g.dart b/packages/profile/lib/providers/profile_providers.g.dart index ee0738c76..b6ccc43c3 100644 --- a/packages/profile/lib/providers/profile_providers.g.dart +++ b/packages/profile/lib/providers/profile_providers.g.dart @@ -6,7 +6,7 @@ part of 'profile_providers.dart'; // RiverpodGenerator // ************************************************************************** -String _$profileEnrollmentHash() => r'2d0c71785bd69ea7867001d029c699fe9567e85c'; +String _$profileEnrollmentHash() => r'a60cd132485bf3a8841f292a07589d819860b009'; /// Provides enrolled courses directly from the DB layer to avoid depending on the `courses` package. ///