diff --git a/openspec/changes/migrate-course-card-duration-to-content/.openspec.yaml b/openspec/changes/migrate-course-card-duration-to-content/.openspec.yaml new file mode 100644 index 00000000..caac5173 --- /dev/null +++ b/openspec/changes/migrate-course-card-duration-to-content/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-03-22 diff --git a/openspec/changes/migrate-course-card-duration-to-content/design.md b/openspec/changes/migrate-course-card-duration-to-content/design.md new file mode 100644 index 00000000..40235d84 --- /dev/null +++ b/openspec/changes/migrate-course-card-duration-to-content/design.md @@ -0,0 +1,41 @@ +## Context + +The `CourseCard` currently displays `totalDuration` (e.g., "180 hrs") as part of its metadata. The Testpress API version 3 provides a more accurate metric, `total_contents`, which counts all learning items including lessons, tests, and assessments. This design outlines the migration to show this new metric. + +## Goals / Non-Goals + +**Goals:** +- Update the domain model and remote parser to include `totalContents`. +- Persist `totalContents` in the local Drift database. +- Update the UI to display "X contents" instead of "Y hrs". +- Ensure backward compatibility and smooth database migration. + +**Non-Goals:** +- Removing `totalDuration` entirely from the codebase in this PR (marked as deprecated). +- Changing the layout of the `CourseCard` beyond the metadata text. + +## Decisions + +### 1. Data Model Update +Add `totalContents` as an `int` to `CourseDto`. +- Mapping: `total_contents` (API) -> `totalContents` (DTO). +- Deprecation: Mark `totalDuration` as `@Deprecated` to signal its replacement. + +### 2. Database Schema (v8) +Add an `IntColumn` for `totalContents` in `CoursesTable`. +- Rationale: Storing it as an integer allows for future sorting/filtering by content volume. +- Migration: Use `m.addColumn(coursesTable, coursesTable.totalContents)` in `AppDatabase.onUpgrade`. + +### 3. Localization +Add `labelContentsPlural` to `app_en.arb` (and other languages) to handle the "contents" suffix. +- Value: `"contents"` (English). + +### 4. Mock Data +Update `MockDataSource` to provide `totalContents` values. For existing mock data, we will use a base value (e.g., 100) or calculate it if feasible. + +## Risks / Trade-offs + +- **[Risk]** Missing `total_contents` in some API responses. + - **Mitigation**: Use a default value of 0 in `RemoteCourseDto.fromJson` and the database column. +- **[Risk]** Database migration overhead. + - **Mitigation**: `addColumn` is an O(1) operation in SQLite and won't block the UI significantly. diff --git a/openspec/changes/migrate-course-card-duration-to-content/proposal.md b/openspec/changes/migrate-course-card-duration-to-content/proposal.md new file mode 100644 index 00000000..518ac7d6 --- /dev/null +++ b/openspec/changes/migrate-course-card-duration-to-content/proposal.md @@ -0,0 +1,25 @@ +## Why + +The current `CourseCard` displays `totalDuration` (e.g., "180 hrs"), which is often an approximation and less informative than the actual count of learning items. To provide students with a better sense of course volume, we are migrating to `totalContents`. This provides a more granular metric that includes lessons, assessments, and tests, aligning with the Testpress API standard. + +## What Changes + +- **Core Data Model**: Update `CourseDto` to include a `totalContents` field. +- **API Mapping**: Update `RemoteCourseDto` to parse `total_contents` from the Testpress API and map it to `CourseDto`. +- **Local Persistence**: Add a `total_contents` column to `CoursesTable` in Drift and implement a non-destructive migration in `AppDatabase`. +- **Mock Data**: Update `MockDataSource` to provide realistic `totalContents` values for development. +- **UI Components**: Update `CourseCard` in the `courses` package to display the content count (e.g., "120 contents") instead of the duration string. + +## Capabilities + +### New Capabilities +- None. + +### Modified Capabilities +- `study-curriculum-list`: The metadata displayed for each course in the curriculum list is updated to show the total number of contents instead of the total duration. + +## Impact + +- **Drift DB**: Requires a schema migration (v7 -> v8). +- **Domain Models**: `CourseDto` modification affects any consumers of this DTO. +- **UI**: Direct change to `CourseCard`'s metadata row. diff --git a/openspec/changes/migrate-course-card-duration-to-content/specs/study-curriculum-list/spec.md b/openspec/changes/migrate-course-card-duration-to-content/specs/study-curriculum-list/spec.md new file mode 100644 index 00000000..44226a2e --- /dev/null +++ b/openspec/changes/migrate-course-card-duration-to-content/specs/study-curriculum-list/spec.md @@ -0,0 +1,8 @@ +## ADDED Requirements + +### Requirement: Course Content Count Visibility +The system SHALL display the total number of contents (lessons, tests, etc.) for each course in the curriculum list. This metric replaces the total duration string to provide a more granular view of the course volume. + +#### Scenario: Displaying content count in CourseCard +- **WHEN** a course has 120 total contents +- **THEN** the course card shows "120 contents" in its metadata section instead of "X hrs" diff --git a/openspec/changes/migrate-course-card-duration-to-content/tasks.md b/openspec/changes/migrate-course-card-duration-to-content/tasks.md new file mode 100644 index 00000000..49e28175 --- /dev/null +++ b/openspec/changes/migrate-course-card-duration-to-content/tasks.md @@ -0,0 +1,13 @@ +## 1. Data Layer (Core Package) + +- [x] 1.1 Update `CourseDto` to include the `totalContents` (int) field and deprecate `totalDuration`. +- [x] 1.2 Update `RemoteCourseDto` to map the API field `total_contents` to `totalContents`. +- [x] 1.3 Add `totalContents` column to `CoursesTable` in the Drift database schema. +- [x] 1.4 Increment `AppDatabase` schema version and implement the non-destructive migration in `onUpgrade`. +- [x] 1.5 Run `build_runner` to regenerate Drift database code. +- [x] 1.6 Update `MockDataSource` to provide `totalContents` for development environments. + +## 2. UI & Localization (Domain Package) + +- [x] 2.1 Add `labelContentsPlural` to localization files (`app_en.arb`, etc.). +- [x] 2.2 Update `CourseCard` to display `totalContents` from `CourseDto` instead of `totalDuration`. diff --git a/packages/core/lib/data/db/app_database.dart b/packages/core/lib/data/db/app_database.dart index 01672834..83e1ac51 100644 --- a/packages/core/lib/data/db/app_database.dart +++ b/packages/core/lib/data/db/app_database.dart @@ -31,7 +31,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 7; + int get schemaVersion => 8; @override MigrationStrategy get migration => MigrationStrategy( @@ -81,11 +81,17 @@ class AppDatabase extends _$AppDatabase { } } - if (from < 7) { - await m.addColumn(coursesTable, coursesTable.image); - } - }, - ); + if (from < 7) { + await m.addColumn(coursesTable, coursesTable.image); + } + + if (from < 8) { + await m.addColumn(coursesTable, coursesTable.totalContents); + // Fill existing rows with lessons count as a fallback until they refresh. + await customStatement('UPDATE courses_table SET total_contents = total_lessons'); + } + }, + ); // ── App Settings ───────────────────────────────────────────────────────── diff --git a/packages/core/lib/data/db/app_database.g.dart b/packages/core/lib/data/db/app_database.g.dart index 334f6907..68262db5 100644 --- a/packages/core/lib/data/db/app_database.g.dart +++ b/packages/core/lib/data/db/app_database.g.dart @@ -60,6 +60,18 @@ class $CoursesTableTable extends CoursesTable type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _totalContentsMeta = const VerificationMeta( + 'totalContents', + ); + @override + late final GeneratedColumn totalContents = GeneratedColumn( + 'total_contents', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0), + ); static const VerificationMeta _progressMeta = const VerificationMeta( 'progress', ); @@ -111,6 +123,7 @@ class $CoursesTableTable extends CoursesTable colorIndex, chapterCount, totalDuration, + totalContents, progress, completedLessons, totalLessons, @@ -171,6 +184,15 @@ class $CoursesTableTable extends CoursesTable } else if (isInserting) { context.missing(_totalDurationMeta); } + if (data.containsKey('total_contents')) { + context.handle( + _totalContentsMeta, + totalContents.isAcceptableOrUnknown( + data['total_contents']!, + _totalContentsMeta, + ), + ); + } if (data.containsKey('progress')) { context.handle( _progressMeta, @@ -232,6 +254,10 @@ class $CoursesTableTable extends CoursesTable DriftSqlType.string, data['${effectivePrefix}total_duration'], )!, + totalContents: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}total_contents'], + )!, progress: attachedDatabase.typeMapping.read( DriftSqlType.int, data['${effectivePrefix}progress'], @@ -264,6 +290,7 @@ class CoursesTableData extends DataClass final int colorIndex; final int chapterCount; final String totalDuration; + final int totalContents; final int progress; final int completedLessons; final int totalLessons; @@ -274,6 +301,7 @@ class CoursesTableData extends DataClass required this.colorIndex, required this.chapterCount, required this.totalDuration, + required this.totalContents, required this.progress, required this.completedLessons, required this.totalLessons, @@ -287,6 +315,7 @@ class CoursesTableData extends DataClass map['color_index'] = Variable(colorIndex); map['chapter_count'] = Variable(chapterCount); map['total_duration'] = Variable(totalDuration); + map['total_contents'] = Variable(totalContents); map['progress'] = Variable(progress); map['completed_lessons'] = Variable(completedLessons); map['total_lessons'] = Variable(totalLessons); @@ -303,6 +332,7 @@ class CoursesTableData extends DataClass colorIndex: Value(colorIndex), chapterCount: Value(chapterCount), totalDuration: Value(totalDuration), + totalContents: Value(totalContents), progress: Value(progress), completedLessons: Value(completedLessons), totalLessons: Value(totalLessons), @@ -323,6 +353,7 @@ class CoursesTableData extends DataClass 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']), @@ -338,6 +369,7 @@ class CoursesTableData extends DataClass '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), @@ -351,6 +383,7 @@ class CoursesTableData extends DataClass int? colorIndex, int? chapterCount, String? totalDuration, + int? totalContents, int? progress, int? completedLessons, int? totalLessons, @@ -361,6 +394,7 @@ class CoursesTableData extends DataClass 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, @@ -379,6 +413,9 @@ class CoursesTableData extends DataClass totalDuration: data.totalDuration.present ? data.totalDuration.value : this.totalDuration, + totalContents: data.totalContents.present + ? data.totalContents.value + : this.totalContents, progress: data.progress.present ? data.progress.value : this.progress, completedLessons: data.completedLessons.present ? data.completedLessons.value @@ -398,6 +435,7 @@ class CoursesTableData extends DataClass ..write('colorIndex: $colorIndex, ') ..write('chapterCount: $chapterCount, ') ..write('totalDuration: $totalDuration, ') + ..write('totalContents: $totalContents, ') ..write('progress: $progress, ') ..write('completedLessons: $completedLessons, ') ..write('totalLessons: $totalLessons, ') @@ -413,6 +451,7 @@ class CoursesTableData extends DataClass colorIndex, chapterCount, totalDuration, + totalContents, progress, completedLessons, totalLessons, @@ -427,6 +466,7 @@ class CoursesTableData extends DataClass 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 && @@ -439,6 +479,7 @@ class CoursesTableCompanion extends UpdateCompanion { final Value colorIndex; final Value chapterCount; final Value totalDuration; + final Value totalContents; final Value progress; final Value completedLessons; final Value totalLessons; @@ -450,6 +491,7 @@ class CoursesTableCompanion extends UpdateCompanion { 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(), @@ -462,6 +504,7 @@ class CoursesTableCompanion extends UpdateCompanion { required int colorIndex, required int chapterCount, required String totalDuration, + this.totalContents = const Value.absent(), this.progress = const Value.absent(), this.completedLessons = const Value.absent(), required int totalLessons, @@ -479,6 +522,7 @@ class CoursesTableCompanion extends UpdateCompanion { Expression? colorIndex, Expression? chapterCount, Expression? totalDuration, + Expression? totalContents, Expression? progress, Expression? completedLessons, Expression? totalLessons, @@ -491,6 +535,7 @@ class CoursesTableCompanion extends UpdateCompanion { 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, @@ -505,6 +550,7 @@ class CoursesTableCompanion extends UpdateCompanion { Value? colorIndex, Value? chapterCount, Value? totalDuration, + Value? totalContents, Value? progress, Value? completedLessons, Value? totalLessons, @@ -517,6 +563,7 @@ class CoursesTableCompanion extends UpdateCompanion { 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, @@ -543,6 +590,9 @@ class CoursesTableCompanion extends UpdateCompanion { if (totalDuration.present) { map['total_duration'] = Variable(totalDuration.value); } + if (totalContents.present) { + map['total_contents'] = Variable(totalContents.value); + } if (progress.present) { map['progress'] = Variable(progress.value); } @@ -569,6 +619,7 @@ class CoursesTableCompanion extends UpdateCompanion { ..write('colorIndex: $colorIndex, ') ..write('chapterCount: $chapterCount, ') ..write('totalDuration: $totalDuration, ') + ..write('totalContents: $totalContents, ') ..write('progress: $progress, ') ..write('completedLessons: $completedLessons, ') ..write('totalLessons: $totalLessons, ') @@ -3715,6 +3766,7 @@ typedef $$CoursesTableTableCreateCompanionBuilder = required int colorIndex, required int chapterCount, required String totalDuration, + Value totalContents, Value progress, Value completedLessons, required int totalLessons, @@ -3728,6 +3780,7 @@ typedef $$CoursesTableTableUpdateCompanionBuilder = Value colorIndex, Value chapterCount, Value totalDuration, + Value totalContents, Value progress, Value completedLessons, Value totalLessons, @@ -3769,6 +3822,11 @@ class $$CoursesTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get totalContents => $composableBuilder( + column: $table.totalContents, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get progress => $composableBuilder( column: $table.progress, builder: (column) => ColumnFilters(column), @@ -3824,6 +3882,11 @@ class $$CoursesTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get totalContents => $composableBuilder( + column: $table.totalContents, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get progress => $composableBuilder( column: $table.progress, builder: (column) => ColumnOrderings(column), @@ -3875,6 +3938,11 @@ class $$CoursesTableTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get totalContents => $composableBuilder( + column: $table.totalContents, + builder: (column) => column, + ); + GeneratedColumn get progress => $composableBuilder(column: $table.progress, builder: (column) => column); @@ -3928,6 +3996,7 @@ class $$CoursesTableTableTableManager 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(), @@ -3939,6 +4008,7 @@ class $$CoursesTableTableTableManager colorIndex: colorIndex, chapterCount: chapterCount, totalDuration: totalDuration, + totalContents: totalContents, progress: progress, completedLessons: completedLessons, totalLessons: totalLessons, @@ -3952,6 +4022,7 @@ class $$CoursesTableTableTableManager required int colorIndex, required int chapterCount, required String totalDuration, + Value totalContents = const Value.absent(), Value progress = const Value.absent(), Value completedLessons = const Value.absent(), required int totalLessons, @@ -3963,6 +4034,7 @@ class $$CoursesTableTableTableManager colorIndex: colorIndex, chapterCount: chapterCount, totalDuration: totalDuration, + totalContents: totalContents, progress: progress, completedLessons: completedLessons, totalLessons: totalLessons, diff --git a/packages/core/lib/data/db/tables/courses_table.dart b/packages/core/lib/data/db/tables/courses_table.dart index 55a3975b..1b704e75 100644 --- a/packages/core/lib/data/db/tables/courses_table.dart +++ b/packages/core/lib/data/db/tables/courses_table.dart @@ -7,6 +7,7 @@ class CoursesTable extends Table { IntColumn get colorIndex => integer()(); IntColumn get chapterCount => integer()(); TextColumn get totalDuration => text()(); + IntColumn get totalContents => integer().withDefault(const Constant(0))(); IntColumn get progress => integer().withDefault(const Constant(0))(); IntColumn get completedLessons => integer().withDefault(const Constant(0))(); IntColumn get totalLessons => integer()(); diff --git a/packages/core/lib/data/models/course_dto.dart b/packages/core/lib/data/models/course_dto.dart index 4ccaf3e6..3e8f89e9 100644 --- a/packages/core/lib/data/models/course_dto.dart +++ b/packages/core/lib/data/models/course_dto.dart @@ -14,7 +14,9 @@ class CourseDto { final int colorIndex; final int chapterCount; + @Deprecated('Use totalContents instead') final String totalDuration; + final int totalContents; final int progress; // 0–100 final int completedLessons; final int totalLessons; @@ -27,6 +29,7 @@ class CourseDto { required this.colorIndex, required this.chapterCount, required this.totalDuration, + required this.totalContents, required this.progress, required this.completedLessons, required this.totalLessons, @@ -40,6 +43,7 @@ class CourseDto { int? colorIndex, int? chapterCount, String? totalDuration, + int? totalContents, int? progress, int? completedLessons, int? totalLessons, @@ -52,6 +56,7 @@ class CourseDto { 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, @@ -67,11 +72,13 @@ class CourseDto { 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 int? ?? 0, completedLessons: json['completed_lessons_count'] as int? ?? 0, totalLessons: json['total_lessons_count'] as int? ?? 0, image: json['image'] as String?, - chapters: (json['chapters'] as List?) + chapters: + (json['chapters'] as List?) ?.map((e) => ChapterDto.fromJson(e as Map)) .toList() ?? const [], @@ -85,6 +92,7 @@ class CourseDto { 'colorIndex': colorIndex, 'chapterCount': chapterCount, 'totalDuration': totalDuration, + 'totalContents': totalContents, 'progress': progress, 'completedLessons': completedLessons, 'totalLessons': totalLessons, diff --git a/packages/core/lib/data/sources/http_data_source.dart b/packages/core/lib/data/sources/http_data_source.dart index 2badb917..2f4af348 100644 --- a/packages/core/lib/data/sources/http_data_source.dart +++ b/packages/core/lib/data/sources/http_data_source.dart @@ -13,7 +13,7 @@ class HttpDataSource implements DataSource { final NetworkProvider _networkProvider; HttpDataSource({required NetworkProvider networkProvider}) - : _networkProvider = networkProvider; + : _networkProvider = networkProvider; @override Future> getCourses({ @@ -47,8 +47,8 @@ class HttpDataSource implements DataSource { @override Future> getLiveClasses() => throw UnimplementedError( - 'HttpDataSource.getLiveClasses is not yet implemented.', - ); + 'HttpDataSource.getLiveClasses is not yet implemented.', + ); @override Future> getForumThreads(String courseId) => @@ -70,13 +70,13 @@ class HttpDataSource implements DataSource { @override Future> getStudyTips() => throw UnimplementedError( - 'HttpDataSource.getStudyTips is not yet implemented.', - ); + 'HttpDataSource.getStudyTips is not yet implemented.', + ); @override Future> getShortLessons() => throw UnimplementedError( - 'HttpDataSource.getShortLessons is not yet implemented.', - ); + 'HttpDataSource.getShortLessons is not yet implemented.', + ); @override Future> getDiscoveryCourses() => diff --git a/packages/core/lib/data/sources/mock_data_source.dart b/packages/core/lib/data/sources/mock_data_source.dart index c8e7a991..a495592c 100644 --- a/packages/core/lib/data/sources/mock_data_source.dart +++ b/packages/core/lib/data/sources/mock_data_source.dart @@ -38,6 +38,7 @@ class MockDataSource implements DataSource { colorIndex: 0, chapterCount: 12, totalDuration: '180 hrs', + totalContents: 120, progress: 34, completedLessons: 28, totalLessons: 84, @@ -48,6 +49,7 @@ class MockDataSource implements DataSource { colorIndex: 4, chapterCount: 10, totalDuration: '160 hrs', + totalContents: 110, progress: 18, completedLessons: 14, totalLessons: 76, @@ -58,6 +60,7 @@ class MockDataSource implements DataSource { colorIndex: 3, chapterCount: 8, totalDuration: '120 hrs', + totalContents: 80, progress: 5, completedLessons: 3, totalLessons: 60, @@ -68,6 +71,7 @@ class MockDataSource implements DataSource { colorIndex: 2, chapterCount: 15, totalDuration: '200 hrs', + totalContents: 150, progress: 45, completedLessons: 45, totalLessons: 100, @@ -78,6 +82,7 @@ class MockDataSource implements DataSource { colorIndex: 5, chapterCount: 6, totalDuration: '40 hrs', + totalContents: 40, progress: 10, completedLessons: 2, totalLessons: 20, @@ -91,6 +96,7 @@ class MockDataSource implements DataSource { colorIndex: 1, chapterCount: 15, totalDuration: '100 hrs', + totalContents: 90, progress: 0, completedLessons: 0, totalLessons: 50, @@ -101,6 +107,7 @@ class MockDataSource implements DataSource { colorIndex: 6, chapterCount: 20, totalDuration: '150 hrs', + totalContents: 130, progress: 12, completedLessons: 10, totalLessons: 80, @@ -114,6 +121,7 @@ class MockDataSource implements DataSource { colorIndex: 7, chapterCount: 5, totalDuration: '20 hrs', + totalContents: 25, progress: 100, completedLessons: 20, totalLessons: 20, diff --git a/packages/core/lib/generated/l10n/app_localizations.dart b/packages/core/lib/generated/l10n/app_localizations.dart index 10878ba8..0c3c573f 100644 --- a/packages/core/lib/generated/l10n/app_localizations.dart +++ b/packages/core/lib/generated/l10n/app_localizations.dart @@ -1138,6 +1138,12 @@ abstract class AppLocalizations { /// **'lessons'** String get labelLessonsPlural; + /// No description provided for @labelContentsPlural. + /// + /// In en, this message translates to: + /// **'contents'** + String get labelContentsPlural; + /// No description provided for @curriculumBackButton. /// /// In en, this message translates to: @@ -1153,9 +1159,15 @@ abstract class AppLocalizations { /// No description provided for @curriculumChaptersCount. /// /// In en, this message translates to: - /// **'{count} Chapters'** + /// **'{count,plural, =1{1 Chapter} other{{count} Chapters}}'** String curriculumChaptersCount(int count); + /// No description provided for @courseContentsCount. + /// + /// In en, this message translates to: + /// **'{count,plural, =1{1 content} other{{count} contents}}'** + String courseContentsCount(int count); + /// No description provided for @filterAll. /// /// In en, this message translates to: diff --git a/packages/core/lib/generated/l10n/app_localizations_ar.dart b/packages/core/lib/generated/l10n/app_localizations_ar.dart index a8e04755..9e6b6f99 100644 --- a/packages/core/lib/generated/l10n/app_localizations_ar.dart +++ b/packages/core/lib/generated/l10n/app_localizations_ar.dart @@ -575,6 +575,9 @@ class AppLocalizationsAr extends AppLocalizations { @override String get labelLessonsPlural => 'دروس'; + @override + String get labelContentsPlural => 'محتويات'; + @override String get curriculumBackButton => 'عودة'; @@ -583,7 +586,24 @@ class AppLocalizationsAr extends AppLocalizations { @override String curriculumChaptersCount(int count) { - return '$count فصول'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count فصول', + one: '1 فصل', + ); + return '$_temp0'; + } + + @override + String courseContentsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count محتويات', + one: '1 المحتوى', + ); + return '$_temp0'; } @override diff --git a/packages/core/lib/generated/l10n/app_localizations_en.dart b/packages/core/lib/generated/l10n/app_localizations_en.dart index 1388ab66..4ee16a05 100644 --- a/packages/core/lib/generated/l10n/app_localizations_en.dart +++ b/packages/core/lib/generated/l10n/app_localizations_en.dart @@ -577,6 +577,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get labelLessonsPlural => 'lessons'; + @override + String get labelContentsPlural => 'contents'; + @override String get curriculumBackButton => 'Back'; @@ -585,7 +588,24 @@ class AppLocalizationsEn extends AppLocalizations { @override String curriculumChaptersCount(int count) { - return '$count Chapters'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count Chapters', + one: '1 Chapter', + ); + return '$_temp0'; + } + + @override + String courseContentsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count contents', + one: '1 content', + ); + return '$_temp0'; } @override diff --git a/packages/core/lib/generated/l10n/app_localizations_ml.dart b/packages/core/lib/generated/l10n/app_localizations_ml.dart index b9237d5d..90eed59d 100644 --- a/packages/core/lib/generated/l10n/app_localizations_ml.dart +++ b/packages/core/lib/generated/l10n/app_localizations_ml.dart @@ -584,6 +584,9 @@ class AppLocalizationsMl extends AppLocalizations { @override String get labelLessonsPlural => 'പാഠങ്ങൾ'; + @override + String get labelContentsPlural => 'ഉള്ളടക്കങ്ങൾ'; + @override String get curriculumBackButton => 'പിന്നിലേക്ക്'; @@ -592,7 +595,24 @@ class AppLocalizationsMl extends AppLocalizations { @override String curriculumChaptersCount(int count) { - return '$count അധ്യായങ്ങൾ'; + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count അധ്യായങ്ങൾ', + one: '1 അധ്യായം', + ); + return '$_temp0'; + } + + @override + String courseContentsCount(int count) { + String _temp0 = intl.Intl.pluralLogic( + count, + locale: localeName, + other: '$count ഉള്ളടക്കങ്ങൾ', + one: '1 ഉള്ളടക്കം', + ); + return '$_temp0'; } @override diff --git a/packages/core/lib/l10n/app_ar.arb b/packages/core/lib/l10n/app_ar.arb index 7c6ca84f..7ede08ce 100644 --- a/packages/core/lib/l10n/app_ar.arb +++ b/packages/core/lib/l10n/app_ar.arb @@ -173,9 +173,11 @@ "errorGenericTitle": "حدث خطأ ما", "errorGenericMessage": "فشل تحميل البيانات. يرجى التحقق من الاتصال والمحاولة مرة أخرى.", "labelLessonsPlural": "دروس", + "labelContentsPlural": "محتويات", "curriculumBackButton": "عودة", "commonCloseButton": "إغلاق", - "curriculumChaptersCount": "{count} فصول", + "curriculumChaptersCount": "{count,plural, =1{1 فصل} other{{count} فصول}}", + "courseContentsCount": "{count,plural, =1{1 المحتوى} other{{count} محتويات}}", "filterAll": "الكل", "curriculumLessonsLabel": "دروس", "curriculumAssessmentsLabel": "تقييمات", diff --git a/packages/core/lib/l10n/app_en.arb b/packages/core/lib/l10n/app_en.arb index 0a3c6fb9..145e773d 100644 --- a/packages/core/lib/l10n/app_en.arb +++ b/packages/core/lib/l10n/app_en.arb @@ -271,9 +271,10 @@ "errorGenericTitle": "Something went wrong", "errorGenericMessage": "Failed to load data. Please check your connection and try again.", "labelLessonsPlural": "lessons", + "labelContentsPlural": "contents", "curriculumBackButton": "Back", "commonCloseButton": "Close", - "curriculumChaptersCount": "{count} Chapters", + "curriculumChaptersCount": "{count,plural, =1{1 Chapter} other{{count} Chapters}}", "@curriculumChaptersCount": { "placeholders": { "count": { @@ -281,6 +282,14 @@ } } }, + "courseContentsCount": "{count,plural, =1{1 content} other{{count} contents}}", + "@courseContentsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, "filterAll": "All", "curriculumLessonsLabel": "Lessons", "curriculumAssessmentsLabel": "Assessments", diff --git a/packages/core/lib/l10n/app_ml.arb b/packages/core/lib/l10n/app_ml.arb index ae5ae881..64f508cc 100644 --- a/packages/core/lib/l10n/app_ml.arb +++ b/packages/core/lib/l10n/app_ml.arb @@ -173,9 +173,11 @@ "errorGenericTitle": "എന്തോ തകരാർ സംഭവിച്ചു", "errorGenericMessage": "ഡാറ്റ ലോഡ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു. ദയവായി കണക്ഷൻ പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക.", "labelLessonsPlural": "പാഠങ്ങൾ", + "labelContentsPlural": "ഉള്ളടക്കങ്ങൾ", "curriculumBackButton": "പിന്നിലേക്ക്", "commonCloseButton": "അടയ്‌ക്കുക", - "curriculumChaptersCount": "{count} അധ്യായങ്ങൾ", + "curriculumChaptersCount": "{count,plural, =1{1 അധ്യായം} other{{count} അധ്യായങ്ങൾ}}", + "courseContentsCount": "{count,plural, =1{1 ഉള്ളടക്കം} other{{count} ഉള്ളടക്കങ്ങൾ}}", "filterAll": "എല്ലാം", "curriculumLessonsLabel": "പാഠങ്ങൾ", "curriculumAssessmentsLabel": "അസസ്‌മെന്റുകൾ", diff --git a/packages/courses/lib/repositories/course_repository.dart b/packages/courses/lib/repositories/course_repository.dart index dce55f58..3e1f712a 100644 --- a/packages/courses/lib/repositories/course_repository.dart +++ b/packages/courses/lib/repositories/course_repository.dart @@ -108,6 +108,7 @@ class CourseRepository { colorIndex: row.colorIndex, chapterCount: row.chapterCount, totalDuration: row.totalDuration, + totalContents: row.totalContents, progress: row.progress, completedLessons: row.completedLessons, totalLessons: row.totalLessons, @@ -121,6 +122,7 @@ class CourseRepository { colorIndex: dto.colorIndex, chapterCount: dto.chapterCount, totalDuration: dto.totalDuration, + totalContents: Value(dto.totalContents), progress: Value(dto.progress), completedLessons: Value(dto.completedLessons), totalLessons: dto.totalLessons, diff --git a/packages/courses/lib/widgets/course_card.dart b/packages/courses/lib/widgets/course_card.dart index 5069201d..556b771e 100644 --- a/packages/courses/lib/widgets/course_card.dart +++ b/packages/courses/lib/widgets/course_card.dart @@ -85,7 +85,7 @@ class CourseCard extends StatelessWidget { // Metadata AppText.caption( - '${course.chapterCount} chapters · ${course.totalDuration}', + '${L10n.of(context).curriculumChaptersCount(course.chapterCount)} · ${L10n.of(context).courseContentsCount(course.totalContents)}', color: design.colors.textSecondary, ), SizedBox(height: design.spacing.md), diff --git a/packages/courses/test/widgets/course_card_test.dart b/packages/courses/test/widgets/course_card_test.dart index 168ef6d0..c9be8293 100644 --- a/packages/courses/test/widgets/course_card_test.dart +++ b/packages/courses/test/widgets/course_card_test.dart @@ -34,6 +34,7 @@ void main() { colorIndex: 0, chapterCount: 5, totalDuration: '10h', + totalContents: 50, progress: 65, completedLessons: 65, totalLessons: 100, @@ -88,6 +89,7 @@ void main() { colorIndex: 0, chapterCount: 3, totalDuration: '6h', + totalContents: 30, progress: 0, completedLessons: 0, totalLessons: 50, diff --git a/packages/profile/lib/providers/certificates_provider.dart b/packages/profile/lib/providers/certificates_provider.dart index d9c9ee0f..70514606 100644 --- a/packages/profile/lib/providers/certificates_provider.dart +++ b/packages/profile/lib/providers/certificates_provider.dart @@ -16,6 +16,7 @@ final List _paidActiveCertificates = [ colorIndex: 3, chapterCount: 24, totalDuration: '48h', + totalContents: 140, progress: 100, completedLessons: 84, totalLessons: 84, @@ -40,6 +41,7 @@ final List _paidActiveCertificates = [ colorIndex: 1, chapterCount: 18, totalDuration: '32h', + totalContents: 100, progress: 45, completedLessons: 27, totalLessons: 60, @@ -56,6 +58,7 @@ final List _paidActiveCertificates = [ colorIndex: 2, chapterCount: 21, totalDuration: '36h', + totalContents: 120, progress: 28, completedLessons: 14, totalLessons: 50, @@ -72,6 +75,7 @@ final List _paidActiveCertificates = [ colorIndex: 3, chapterCount: 16, totalDuration: '30h', + totalContents: 90, progress: 12, completedLessons: 8, totalLessons: 66, diff --git a/packages/profile/lib/providers/profile_providers.dart b/packages/profile/lib/providers/profile_providers.dart index 5e6f33d1..607244c7 100644 --- a/packages/profile/lib/providers/profile_providers.dart +++ b/packages/profile/lib/providers/profile_providers.dart @@ -22,6 +22,7 @@ Stream> profileEnrollment(Ref ref) async* { colorIndex: row.colorIndex, chapterCount: row.chapterCount, totalDuration: row.totalDuration, + totalContents: row.totalContents, progress: row.progress, completedLessons: row.completedLessons, totalLessons: row.totalLessons,