diff --git a/assets/translations/en.json b/assets/translations/en.json index 859ce9f..e1c2f49 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -21,7 +21,7 @@ "create_password": "Create password", "confirm_password": "Confirm password", "eight_character_minimum": "8 characters minimum", - "password_math": "password match", + "password_match": "password match", "sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’", "terms_of_services": "Terms of Service", "and": "and", diff --git a/lib/models/access.dart b/lib/models/access.dart index 96725a9..9bb0997 100644 --- a/lib/models/access.dart +++ b/lib/models/access.dart @@ -1,7 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; part 'access.g.dart'; - @JsonSerializable() class Access { final String? reason; @@ -21,15 +20,43 @@ class Access { @JsonKey(name: 'progress_percent') final int? progressPercent; - const Access( - {this.reason, - this.totalCount, - this.isCompleted, - this.isAccessible, - this.completedCount, - this.progressPercent}); + @JsonKey(name: 'progress_percent_precise') + final int? progressPercentPrecise; - factory Access.fromJson(Map json) => _$AccessFromJson(json); + const Access({ + this.reason, + this.totalCount, + this.isCompleted, + this.isAccessible, + this.completedCount, + this.progressPercent, + this.progressPercentPrecise, + }); + + Access copyWith({ + String? reason, + int? totalCount, + bool? isCompleted, + bool? isAccessible, + int? completedCount, + int? progressPercent, + int? progressPercentPrecise, + }) { + return Access( + reason: reason ?? this.reason, + totalCount: totalCount ?? this.totalCount, + isCompleted: isCompleted ?? this.isCompleted, + isAccessible: isAccessible ?? this.isAccessible, + progressPercentPrecise: + progressPercentPrecise ?? this.progressPercentPrecise, + completedCount: completedCount ?? this.completedCount, + progressPercent: progressPercent ?? this.progressPercent, + + ); + } + + factory Access.fromJson(Map json) => + _$AccessFromJson(json); Map toJson() => _$AccessToJson(this); -} +} \ No newline at end of file diff --git a/lib/models/access.g.dart b/lib/models/access.g.dart index 01350af..27f3d64 100644 --- a/lib/models/access.g.dart +++ b/lib/models/access.g.dart @@ -13,6 +13,8 @@ Access _$AccessFromJson(Map json) => Access( isAccessible: json['is_accessible'] as bool?, completedCount: (json['completed_count'] as num?)?.toInt(), progressPercent: (json['progress_percent'] as num?)?.toInt(), + progressPercentPrecise: + (json['progress_percent_precise'] as num?)?.toInt(), ); Map _$AccessToJson(Access instance) => { @@ -22,4 +24,5 @@ Map _$AccessToJson(Access instance) => { 'is_accessible': instance.isAccessible, 'completed_count': instance.completedCount, 'progress_percent': instance.progressPercent, + 'progress_percent_precise': instance.progressPercentPrecise, }; diff --git a/lib/models/course_progress.dart b/lib/models/course_progress.dart index e2741a0..ed7a565 100644 --- a/lib/models/course_progress.dart +++ b/lib/models/course_progress.dart @@ -1,42 +1,30 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:yimaru_app/models/module_progress.dart'; + +import 'access.dart'; part 'course_progress.g.dart'; @JsonSerializable() class CourseProgress { - final String? level; + final int? id; - final String? title; + final String? name; - final String? description; + final Access? access; - @JsonKey(name: 'is_locked') - final bool? isLocked; + final List? modules; - @JsonKey(name: 'sub_course_id') - final int? courseId; - @JsonKey(name: 'display_order') - final int? displayOrder; + @JsonKey(name: 'program_id') + final int? programId; - @JsonKey(name: 'progress_status') - final String? progressStatus; - @JsonKey(name: 'progress_percentage') - final double? progressPercentage; const CourseProgress( - {this.level, - this.title, - this.isLocked, - this.courseId, - this.description, - this.displayOrder, - this.progressStatus, - this.progressPercentage}); + {this.id,this.name,this.access,this.modules,this.programId}); - factory CourseProgress.fromJson(Map json) => - _$CourseProgressFromJson(json); + factory CourseProgress.fromJson(Map json) => _$CourseProgressFromJson(json); Map toJson() => _$CourseProgressToJson(this); } diff --git a/lib/models/course_progress.g.dart b/lib/models/course_progress.g.dart index d883ecf..c9909d8 100644 --- a/lib/models/course_progress.g.dart +++ b/lib/models/course_progress.g.dart @@ -8,24 +8,22 @@ part of 'course_progress.dart'; CourseProgress _$CourseProgressFromJson(Map json) => CourseProgress( - level: json['level'] as String?, - title: json['title'] as String?, - isLocked: json['is_locked'] as bool?, - courseId: (json['sub_course_id'] as num?)?.toInt(), - description: json['description'] as String?, - displayOrder: (json['display_order'] as num?)?.toInt(), - progressStatus: json['progress_status'] as String?, - progressPercentage: (json['progress_percentage'] as num?)?.toDouble(), + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + access: json['access'] == null + ? null + : Access.fromJson(json['access'] as Map), + modules: (json['modules'] as List?) + ?.map((e) => ModuleProgress.fromJson(e as Map)) + .toList(), + programId: (json['program_id'] as num?)?.toInt(), ); Map _$CourseProgressToJson(CourseProgress instance) => { - 'level': instance.level, - 'title': instance.title, - 'description': instance.description, - 'is_locked': instance.isLocked, - 'sub_course_id': instance.courseId, - 'display_order': instance.displayOrder, - 'progress_status': instance.progressStatus, - 'progress_percentage': instance.progressPercentage, + 'id': instance.id, + 'name': instance.name, + 'access': instance.access, + 'modules': instance.modules, + 'program_id': instance.programId, }; diff --git a/lib/models/learn_course.dart b/lib/models/learn_course.dart index 4605a5a..2a30db9 100644 --- a/lib/models/learn_course.dart +++ b/lib/models/learn_course.dart @@ -19,16 +19,36 @@ class LearnCourse { @JsonKey(name: 'program_id') final int? programId; - const LearnCourse( - {this.id, - this.name, - this.access, - this.programId, - this.sortOrder, - this.description}); + const LearnCourse({ + this.id, + this.name, + this.access, + this.programId, + this.sortOrder, + this.description, + }); + + LearnCourse copyWith({ + int? id, + String? name, + Access? access, + int? sortOrder, + int? programId, + String? description, + + }) { + return LearnCourse( + id: id ?? this.id, + name: name ?? this.name, + access: access ?? this.access, + sortOrder: sortOrder ?? this.sortOrder, + programId: programId ?? this.programId, + description: description ?? this.description, + ); + } factory LearnCourse.fromJson(Map json) => _$LearnCourseFromJson(json); Map toJson() => _$LearnCourseToJson(this); -} +} \ No newline at end of file diff --git a/lib/models/learn_lesson.dart b/lib/models/learn_lesson.dart index 4728d9b..c611006 100644 --- a/lib/models/learn_lesson.dart +++ b/lib/models/learn_lesson.dart @@ -3,7 +3,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'access.dart'; part 'learn_lesson.g.dart'; - @JsonSerializable() class LearnLesson { final int? id; @@ -36,8 +35,31 @@ class LearnLesson { this.description, }); + LearnLesson copyWith({ + int? id, + String? title, + int? moduleId, + int? sortOrder, + Access? access, + String? videoUrl, + String? thumbnail, + String? description, + + }) { + return LearnLesson( + id: id ?? this.id, + title: title ?? this.title, + access: access ?? this.access, + videoUrl: videoUrl ?? this.videoUrl, + moduleId: moduleId ?? this.moduleId, + sortOrder: sortOrder ?? this.sortOrder, + thumbnail: thumbnail ?? this.thumbnail, + description: description ?? this.description, + ); + } + factory LearnLesson.fromJson(Map json) => _$LearnLessonFromJson(json); Map toJson() => _$LearnLessonToJson(this); -} +} \ No newline at end of file diff --git a/lib/models/learn_module.dart b/lib/models/learn_module.dart index b444229..1487aac 100644 --- a/lib/models/learn_module.dart +++ b/lib/models/learn_module.dart @@ -2,7 +2,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:yimaru_app/models/access.dart'; part 'learn_module.g.dart'; - @JsonSerializable() class LearnModule { final int? id; @@ -24,18 +23,43 @@ class LearnModule { @JsonKey(name: 'sort_order') final int? sortOrder; - const LearnModule( - {this.id, - this.icon, - this.name, - this.access, - this.courseId, - this.sortOrder, - this.programId, - this.description}); + const LearnModule({ + this.id, + this.icon, + this.name, + this.access, + this.courseId, + this.sortOrder, + this.programId, + this.description, + }); + + LearnModule copyWith({ + int? id, + String? icon, + String? name, + int? courseId, + Access? access, + int? programId, + int? sortOrder, + String? description, + + }) { + return LearnModule( + id: id ?? this.id, + icon: icon ?? this.icon, + name: name ?? this.name, + access: access ?? this.access, + courseId: courseId ?? this.courseId, + sortOrder: sortOrder ?? this.sortOrder, + programId: programId ?? this.programId, + description: description ?? this.description, + + ); + } factory LearnModule.fromJson(Map json) => _$LearnModuleFromJson(json); Map toJson() => _$LearnModuleToJson(this); -} +} \ No newline at end of file diff --git a/lib/models/learn_program.dart b/lib/models/learn_program.dart index bfe32f6..191fd93 100644 --- a/lib/models/learn_program.dart +++ b/lib/models/learn_program.dart @@ -2,7 +2,6 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:yimaru_app/models/access.dart'; part 'learn_program.g.dart'; - @JsonSerializable() class LearnProgram { final int? id; @@ -16,11 +15,32 @@ class LearnProgram { @JsonKey(name: 'sort_order') final int? sortOrder; - const LearnProgram( - {this.id, this.name, this.access, this.sortOrder, this.description}); + const LearnProgram({ + this.id, + this.name, + this.access, + this.sortOrder, + this.description, + }); + + LearnProgram copyWith({ + int? id, + String? name, + Access? access, + int? sortOrder, + String? description, + }) { + return LearnProgram( + id: id ?? this.id, + name: name ?? this.name, + access: access ?? this.access, + sortOrder: sortOrder ?? this.sortOrder, + description: description ?? this.description, + ); + } factory LearnProgram.fromJson(Map json) => _$LearnProgramFromJson(json); Map toJson() => _$LearnProgramToJson(this); -} +} \ No newline at end of file diff --git a/lib/models/lesson_progress.dart b/lib/models/lesson_progress.dart new file mode 100644 index 0000000..c661938 --- /dev/null +++ b/lib/models/lesson_progress.dart @@ -0,0 +1,25 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'access.dart'; + +part 'lesson_progress.g.dart'; + +@JsonSerializable() +class LessonProgress { + final int? id; + + final String? title; + + final Access? access; + + @JsonKey(name: 'module_id') + final int? moduleId; + + + const LessonProgress( + {this.id,this.title,this.access,this.moduleId}); + + factory LessonProgress.fromJson(Map json) => _$LessonProgressFromJson(json); + + Map toJson() => _$LessonProgressToJson(this); +} diff --git a/lib/models/lesson_progress.g.dart b/lib/models/lesson_progress.g.dart new file mode 100644 index 0000000..74a920a --- /dev/null +++ b/lib/models/lesson_progress.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'lesson_progress.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +LessonProgress _$LessonProgressFromJson(Map json) => + LessonProgress( + id: (json['id'] as num?)?.toInt(), + title: json['title'] as String?, + access: json['access'] == null + ? null + : Access.fromJson(json['access'] as Map), + moduleId: (json['module_id'] as num?)?.toInt(), + ); + +Map _$LessonProgressToJson(LessonProgress instance) => + { + 'id': instance.id, + 'title': instance.title, + 'access': instance.access, + 'module_id': instance.moduleId, + }; diff --git a/lib/models/module_progress.dart b/lib/models/module_progress.dart new file mode 100644 index 0000000..198d212 --- /dev/null +++ b/lib/models/module_progress.dart @@ -0,0 +1,36 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yimaru_app/models/lesson_progress.dart'; + +import 'access.dart'; + +part 'module_progress.g.dart'; + +@JsonSerializable() +class ModuleProgress { + final int? id; + + final String? name; + + final Access? access; + + final List? lessons; + + @JsonKey(name: 'course_id') + final int? courseId; + + @JsonKey(name: 'program_id') + final int? programId; + + const ModuleProgress( + {this.id, + this.name, + this.access, + this.lessons, + this.courseId, + this.programId}); + + factory ModuleProgress.fromJson(Map json) => + _$ModuleProgressFromJson(json); + + Map toJson() => _$ModuleProgressToJson(this); +} diff --git a/lib/models/module_progress.g.dart b/lib/models/module_progress.g.dart new file mode 100644 index 0000000..2447d99 --- /dev/null +++ b/lib/models/module_progress.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'module_progress.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ModuleProgress _$ModuleProgressFromJson(Map json) => + ModuleProgress( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + access: json['access'] == null + ? null + : Access.fromJson(json['access'] as Map), + lessons: (json['lessons'] as List?) + ?.map((e) => LessonProgress.fromJson(e as Map)) + .toList(), + courseId: (json['course_id'] as num?)?.toInt(), + programId: (json['program_id'] as num?)?.toInt(), + ); + +Map _$ModuleProgressToJson(ModuleProgress instance) => + { + 'id': instance.id, + 'name': instance.name, + 'access': instance.access, + 'lessons': instance.lessons, + 'course_id': instance.courseId, + 'program_id': instance.programId, + }; diff --git a/lib/models/progress_summary.dart b/lib/models/progress_summary.dart new file mode 100644 index 0000000..350eec6 --- /dev/null +++ b/lib/models/progress_summary.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:yimaru_app/models/course_progress.dart'; + +import 'access.dart'; + +part 'progress_summary.g.dart'; + +@JsonSerializable() +class ProgressSummary { + final int? id; + + final String? name; + + final Access? access; + + final List? courses; + + const ProgressSummary( + {this.id,this.name,this.access,this.courses}); + + factory ProgressSummary.fromJson(Map json) => _$ProgressSummaryFromJson(json); + + Map toJson() => _$ProgressSummaryToJson(this); +} diff --git a/lib/models/progress_summary.g.dart b/lib/models/progress_summary.g.dart new file mode 100644 index 0000000..a6d97f1 --- /dev/null +++ b/lib/models/progress_summary.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'progress_summary.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ProgressSummary _$ProgressSummaryFromJson(Map json) => + ProgressSummary( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + access: json['access'] == null + ? null + : Access.fromJson(json['access'] as Map), + courses: (json['courses'] as List?) + ?.map((e) => CourseProgress.fromJson(e as Map)) + .toList(), + ); + +Map _$ProgressSummaryToJson(ProgressSummary instance) => + { + 'id': instance.id, + 'name': instance.name, + 'access': instance.access, + 'courses': instance.courses, + }; diff --git a/lib/models/refresh_object.dart b/lib/models/refresh_object.dart new file mode 100644 index 0000000..e8a3092 --- /dev/null +++ b/lib/models/refresh_object.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'refresh_object.g.dart'; + +@JsonSerializable() +class RefreshObject { + final String? url; + + @JsonKey(name: 'object_key') + final String? objectKey; + + const RefreshObject({this.url, this.objectKey}); + + factory RefreshObject.fromJson(Map json) => + _$RefreshObjectFromJson(json); + + Map toJson() => _$RefreshObjectToJson(this); +} diff --git a/lib/models/refresh_object.g.dart b/lib/models/refresh_object.g.dart new file mode 100644 index 0000000..83f2825 --- /dev/null +++ b/lib/models/refresh_object.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'refresh_object.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RefreshObject _$RefreshObjectFromJson(Map json) => + RefreshObject( + url: json['url'] as String?, + objectKey: json['object_key'] as String?, + ); + +Map _$RefreshObjectToJson(RefreshObject instance) => + { + 'url': instance.url, + 'object_key': instance.objectKey, + }; diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 555b1e3..8107694 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -6,6 +6,7 @@ import 'package:yimaru_app/models/learn_program.dart'; import 'package:yimaru_app/models/assessment_question.dart'; import 'package:yimaru_app/models/course_catalog.dart'; import 'package:yimaru_app/models/course_lesson.dart'; +import 'package:yimaru_app/models/refresh_object.dart'; import 'package:yimaru_app/models/user.dart'; import 'package:yimaru_app/services/dio_service.dart'; import 'package:yimaru_app/ui/common/app_constants.dart'; @@ -20,6 +21,7 @@ import '../models/learn_question.dart'; import '../models/learn_subscription.dart'; import '../models/assessment.dart'; import '../models/learn_subscription_request.dart'; +import '../models/progress_summary.dart'; import '../ui/common/enmus.dart'; class ApiService { @@ -621,6 +623,34 @@ class ApiService { } } + // Complete profile + Future> refreshObject(Map data) async { + try { + Response response = await _service.dio.post( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kFilesUrl/$kRefreshUrl', + data: data, + ); + + if (response.statusCode == 200) { + return { + 'status': ResponseStatus.success, + 'message': 'Operation successful', + 'data': RefreshObject.fromJson(response.data['data']) + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; + } + } on DioException catch (e) { + return { + 'status': ResponseStatus.failure, + 'message': e.response?.data.toString(), + }; + } + } + // Learn learn programs Future> getLearnPrograms() async { try { @@ -841,6 +871,30 @@ class ApiService { } } + // Get progress summary + Future> getProgressSummary() async { + try { + List summaries = []; + + final Response response = await _service.dio + .get('$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kLmsUrl/$kProgressSummary'); + + if (response.statusCode == 200) { + var data = response.data; + var decodedData = data['data']['programs'] as List; + summaries = decodedData.map( + (e) { + return ProgressSummary.fromJson(e); + }, + ).toList(); + return summaries; + } + return []; + } catch (e) { + return []; + } + } + // Complete lesson Future> completeLearnPractice(int id) async { try { @@ -898,6 +952,32 @@ class ApiService { '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kSubscribeUrl', data: data); + if (response.statusCode == 200) { + return { + 'status': ResponseStatus.success, + 'message': 'Subscription successful!', + 'data': LearnSubscriptionRequest.fromJson(response.data['data']), + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; + } + } on DioException catch (e) { + return { + 'status': ResponseStatus.failure, + 'message': e.response?.data.toString(), + }; + } + } + + // Verify subscription + Future> verifySubscription(int id) async { + try { + Response response = await _service.dio.get( + '$kBaseUrl/$kApiUrl/$kApiVersionUrl/$kPaymentsUrl/$kVerifySubscriptionUrl/$id'); + if (response.statusCode == 200) { return { 'message': 'Lesson completed', diff --git a/lib/services/learn_service.dart b/lib/services/learn_service.dart index 1c978b4..180eafc 100644 --- a/lib/services/learn_service.dart +++ b/lib/services/learn_service.dart @@ -1,10 +1,15 @@ +import 'package:http/http.dart'; import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/models/refresh_object.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; import '../app/app.locator.dart'; +import '../models/access.dart'; import '../models/learn_course.dart'; import '../models/learn_lesson.dart'; import '../models/learn_module.dart'; import '../models/learn_program.dart'; +import '../models/progress_summary.dart'; import 'api_service.dart'; class LearnService with ListenableServiceMixin { @@ -36,6 +41,24 @@ class LearnService with ListenableServiceMixin { List get lessons => _lessons; + // Learn progress + List _summaries = []; + + List get summaries => _summaries; + + // Learn programs + Future refreshObject(String url) async { + Map data = {'reference': url}; + Map response = await _apiService.refreshObject(data); + + if (response['status'] == ResponseStatus.success) { + RefreshObject object = response['data'] as RefreshObject; + + return object.url ?? ''; + } + return null; + } + // Learn programs Future getLearnPrograms() async { _programs = await _apiService.getLearnPrograms(); @@ -63,4 +86,77 @@ class LearnService with ListenableServiceMixin { _lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0)); notifyListeners(); } + + // Learn progress + Future getLearnProgressSummary() async { + final summaries = await _apiService.getProgressSummary(); + print('MY SUMMARIES: ${summaries.length}'); + + /// PROGRAM ACCESS MAP + final Map programAccessMap = {}; + + /// COURSE ACCESS MAP + final Map courseAccessMap = {}; + + /// MODULE ACCESS MAP + final Map moduleAccessMap = {}; + + /// LESSON ACCESS MAP + final Map lessonAccessMap = {}; + + // Build maps + for (final summary in summaries) { + if (summary.id != null) { + programAccessMap[summary.id!] = summary.access; + } + + for (final course in summary.courses ?? []) { + if (course.id != null) { + courseAccessMap[course.id!] = course.access; + } + + for (final module in course.modules ?? []) { + if (module.id != null) { + moduleAccessMap[module.id!] = module.access; + } + + for (final lesson in module.lessons ?? []) { + if (lesson.id != null) { + lessonAccessMap[lesson.id!] = lesson.access; + } + } + } + } + } + + /// UPDATE PROGRAMS + _programs = _programs.map((program) { + return program.copyWith( + access: programAccessMap[program.id] ?? program.access, + ); + }).toList(); + + /// UPDATE COURSES + _courses = _courses.map((course) { + return course.copyWith( + access: courseAccessMap[course.id] ?? course.access, + ); + }).toList(); + + /// UPDATE MODULES + _modules = _modules.map((module) { + return module.copyWith( + access: moduleAccessMap[module.id] ?? module.access, + ); + }).toList(); + + /// UPDATE LESSONS + _lessons = _lessons.map((lesson) { + return lesson.copyWith( + access: lessonAccessMap[lesson.id] ?? lesson.access, + ); + }).toList(); + + notifyListeners(); + } } diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 9c1cd8c..24cb8ea 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -130,7 +130,7 @@ class NotificationService { } Future updateFCMToken() async { - print('DEVICE TOKEN: ${await _messaging.getToken()}'); + // print('DEVICE TOKEN: ${await _messaging.getToken()}'); _messaging.onTokenRefresh.listen((newToken) { // updateTokenOnServer(newToken); }); diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 22c675f..d376bd3 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -1,6 +1,8 @@ // Endpoints String kBaseUrl = 'https://api.yimaruacademy.com'; +String kLmsUrl = 'lms'; + String kAppUrl = 'app'; String kApiUrl = 'api'; @@ -9,6 +11,8 @@ String kUnitsUrl = 'units'; String kCheckUrl = 'check'; +String kFilesUrl = 'files'; + String kApiVersionUrl = 'v1'; String kLevelsUrl = 'levels'; @@ -31,13 +35,15 @@ String kCompleteUrl = 'complete'; String kPaymentsUrl = 'payments'; +String kExamPrepUrl = 'exam-prep'; + String kSubscribeUrl = 'subscribe'; String kPracticesUrl = 'practices'; String kQuestionsUrl = 'questions'; -String kExamPrepUrl = 'exam-prep'; +String kRefreshUrl = 'refresh-url'; String kCoursePractice = 'by-owner'; @@ -57,12 +63,16 @@ String kFieldOptions = 'field-options'; String kResetPassword = 'resetPassword'; +String kVerifySubscriptionUrl = 'verify'; + String kQuestionSetsUrl = 'question-sets'; String kRequestResetCode = 'sendResetCode'; String kSubcategoriesUrl = 'sub-categories'; +String kProgressSummary = 'progress-summary'; + String kPublishedVideos = 'videos/published'; String kCoursePracticeQuestions = 'questions'; diff --git a/lib/ui/common/translations/codegen_loader.g.dart b/lib/ui/common/translations/codegen_loader.g.dart index 38d1e96..f95e766 100644 --- a/lib/ui/common/translations/codegen_loader.g.dart +++ b/lib/ui/common/translations/codegen_loader.g.dart @@ -223,7 +223,7 @@ static const Map _en = { "create_password": "Create password", "confirm_password": "Confirm password", "eight_character_minimum": "8 characters minimum", - "password_math": "password match", + "password_match": "password match", "sign_up_agreement": "By clicking ‘Sign Up’, you agree to our ‘Terms of Service’ and ‘Privacy Policy’", "terms_of_services": "Terms of Service", "and": "and", diff --git a/lib/ui/views/arif_pay/arif_pay_view.dart b/lib/ui/views/arif_pay/arif_pay_view.dart index 5cbbe4c..ffce663 100644 --- a/lib/ui/views/arif_pay/arif_pay_view.dart +++ b/lib/ui/views/arif_pay/arif_pay_view.dart @@ -15,17 +15,10 @@ class ArifPayView extends StackedView { void _pop(ArifPayViewModel viewModel) => viewModel.pop; - Future _error() async { - // await Navigator.pushNamed(context, AppRoutes.subscriptionErrorPage); - // Navigation.pop(); - } + void _error(ArifPayViewModel viewModel) => viewModel.pop(); - void _success() { - // Navigation.navigateTo( - // AppRoutes.subscriptionSuccessPage, - // arguments: widget.body, - // ); - } + Future _success(ArifPayViewModel viewModel) async => + await viewModel.replaceWithHome(); @override void onViewModelReady(ArifPayViewModel viewModel) async { @@ -58,21 +51,12 @@ class ArifPayView extends StackedView { Widget _buildBody(ArifPayViewModel viewModel) => InAppWebView( initialUrlRequest: URLRequest(url: WebUri(viewModel.request?.paymentUrl ?? '')), - onUpdateVisitedHistory: (controller, url, androidIsReload) { - if (url - .toString() - .contains("https://checkout.arifpay.net/canceled")) { - showErrorToast('Operation was cancelled'); - // _pop(); - } else if (url.toString().contains(kSuccessUrl)) { - _success(); + onUpdateVisitedHistory: (controller, url, androidIsReload) async { + if (url.toString().contains(kSuccessUrl)) { + showSuccessToast('Subscription successful, activation in progress!'); + _success(viewModel); } else if (url.toString().contains(kErrorUrl)) { - showErrorToast('Operation was cancelled'); - // _pop(); - } else if (url.toString().contains("http://x.com/elonmusk/status/")) { - _error(); - } else if (url.toString().contains(kErrorUrl)) { - _error(); + _error(viewModel); } }, ); diff --git a/lib/ui/views/arif_pay/arif_pay_viewmodel.dart b/lib/ui/views/arif_pay/arif_pay_viewmodel.dart index 79d8595..540fd6f 100644 --- a/lib/ui/views/arif_pay/arif_pay_viewmodel.dart +++ b/lib/ui/views/arif_pay/arif_pay_viewmodel.dart @@ -3,6 +3,7 @@ import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; +import '../../../app/app.router.dart'; import '../../../models/learn_subscription_request.dart'; import '../../../services/api_service.dart'; import '../../../services/status_checker_service.dart'; @@ -24,6 +25,9 @@ class ArifPayViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); + Future replaceWithHome() async => + await _navigationService.clearStackAndShow(Routes.homeView); + // Remote api call // Learn subscription @@ -36,7 +40,8 @@ class ArifPayViewModel extends BaseViewModel { Map data = { 'plan_id': 1, 'phone': '251$phone', - 'email': 'test@gmail.com' + 'provider': 'ARIFPAY', + 'email': 'test@gmail.com', }; Map response = @@ -48,24 +53,4 @@ class ArifPayViewModel extends BaseViewModel { } } - // - // Future verifyLearnSubscription(String id) async => await runBusyFuture(_verifyLearnSubscription(phone), - // busyObject: StateObjects.learnSubscription); - // - // Future _verifyLearnSubscription(String id) async { - // if (await _statusChecker.checkConnection()) { - // Map data = { - // 'plan_id':1, - // 'phone': '251$phone', - // 'email':'test@gmail.com' - // }; - // - // Map response = - // await _apiService.createSubscriptionRequest(data); - // - // if (response['status'] == ResponseStatus.success) { - // _request = response['data']; - // } - // } - // } } diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index 0c4a729..4f93865 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -10,6 +10,7 @@ import '../../../app/app.locator.dart'; import '../../../models/learn_module.dart'; import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; +import '../../common/helper_functions.dart'; class LearnLessonViewModel extends ReactiveViewModel { // Dependency injection @@ -23,6 +24,11 @@ class LearnLessonViewModel extends ReactiveViewModel { List get listenableServices => [_learnService]; // Learn lessons + + final Map _refreshedThumbnails = {}; + + Map get refreshedThumbnails => _refreshedThumbnails; + List get _lessons => _learnService.lessons; List get lessons => _lessons; @@ -55,6 +61,27 @@ class LearnLessonViewModel extends ReactiveViewModel { Future _getLessons(int id) async { if (await _statusChecker.checkConnection()) { await _learnService.getLearnLessons(id); + // await refreshLessonImages(_lessons); } } + + //Refresh image + Future refreshLessonImages(List lessons) async { + for (final lesson in lessons) { + final thumbnail = lesson.thumbnail; + + if (lesson.id == null || thumbnail == null || thumbnail.isEmpty) { + continue; + } + + final String? refreshedUrl = await _learnService.refreshObject(thumbnail); + + if (refreshedUrl != null) { + _refreshedThumbnails[lesson.id!] = refreshedUrl; + } + } + } + + String getLessonImage(LearnLesson lesson) => + getReadableUrl(_refreshedThumbnails[lesson.id] ?? '') ?? ''; } diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index 85b20d8..5568d24 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -3,12 +3,14 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/models/learn_module.dart'; +import 'package:yimaru_app/models/refresh_object.dart'; import 'package:yimaru_app/ui/common/translations/locale_keys.g.dart'; import '../../../app/app.locator.dart'; import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/enmus.dart'; +import '../../common/helper_functions.dart'; class LearnModuleViewModel extends ReactiveViewModel { // Dependency injection @@ -22,6 +24,10 @@ class LearnModuleViewModel extends ReactiveViewModel { List get listenableServices => [_learnService]; // Learn module + final Map _refreshedIcons = {}; + + Map get refreshedIcons => _refreshedIcons; + List get _modules => _learnService.modules; List get modules => _modules; @@ -52,6 +58,28 @@ class LearnModuleViewModel extends ReactiveViewModel { Future _getLearnModules(int id) async { if (await _statusChecker.checkConnection()) { await _learnService.getLearnModules(id); + await refreshModuleImages(_modules); } } + + //Refresh image + Future refreshModuleImages(List modules) async { + for (final module in modules) { + final icon = module.icon; + + if (module.id == null || icon == null || icon.isEmpty) { + continue; + } + + final String? refreshedUrl = await _learnService.refreshObject(icon); + + if (refreshedUrl != null) { + _refreshedIcons[module.id!] = refreshedUrl; + } + } + } + + String getModuleImage(LearnModule module) => + getReadableUrl(_refreshedIcons[module.id] ?? '') ?? ''; + } diff --git a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart index a6a5148..ebf3d41 100644 --- a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -16,6 +16,7 @@ import '../../../services/audio_player_service.dart'; import '../../../services/learn_service.dart'; import '../../../services/status_checker_service.dart'; import '../../common/app_colors.dart'; +import '../../common/helper_functions.dart'; class LearnPracticeViewModel extends ReactiveViewModel { // Dependency injection @@ -94,10 +95,16 @@ class LearnPracticeViewModel extends ReactiveViewModel { Voice? get playing => _playing; // Learn practices + + List _practices = []; List get practices => _practices; + final Map _refreshedImages= {}; + + Map get refreshedImages => _refreshedImages; + // Practice questions List _questions = []; @@ -244,6 +251,7 @@ class LearnPracticeViewModel extends ReactiveViewModel { ); await playVoicePrompt(_questions[index]); } else { + await completeLearnPractices(); goTo(3); } } @@ -260,6 +268,10 @@ class LearnPracticeViewModel extends ReactiveViewModel { // Remote api call + // Refresh url + Future refreshUrl(String url) async => + await _learnService.refreshObject(url); + // Learn practice Future getLearnPractices( {required int id, required LearnPractices practice}) async => @@ -271,14 +283,14 @@ class LearnPracticeViewModel extends ReactiveViewModel { if (await _statusChecker.checkConnection()) { if (practice == LearnPractices.course) { _practices = await _apiService.getLearnCoursePractices(id); - + // await refreshPracticeImages(_practices); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); } else if (practice == LearnPractices.module) { _practices = await _apiService.getLearnModulePractices(id); + // await refreshPracticeImages(_practices); await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); } else { _practices = await _apiService.getLearnLessonPractices(id); - await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0); } } @@ -296,6 +308,28 @@ class LearnPracticeViewModel extends ReactiveViewModel { Future _completeLearnPractices() async { if (await _statusChecker.checkConnection()) { await _apiService.completeLearnPractice(_practices.first.id ?? 0); + await _learnService.getLearnProgressSummary(); } } + +//Refresh image + Future refreshPracticeImages(List practices) async { + for (final practice in practices) { + final image = practice.storyImage; + + if (practice.id == null || image == null || image.isEmpty) { + continue; + } + + final String? refreshedUrl = await _learnService.refreshObject(image); + + if (refreshedUrl != null) { + _refreshedImages[practice.id!] = refreshedUrl; + } + } + } + + String getPracticeImage(LearnPractice practice) => + getReadableUrl(_refreshedImages[practice.id] ?? '') ?? ''; + } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart index f1260be..55eb813 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_appreciation_screen.dart @@ -15,8 +15,6 @@ class LearnPracticeAppreciationScreen extends ViewModelWidget { const LearnPracticeAppreciationScreen({super.key}); - Future _reset(LearnPracticeViewModel viewModel) async => - await viewModel.reset(); Future _cancel(LearnPracticeViewModel viewModel) async { await viewModel.stopRecording(); diff --git a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart index 6928e7e..cf43223 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_description_screen.dart @@ -163,10 +163,13 @@ class LearnPracticeDescriptionScreen Widget _buildImage(LearnPracticeViewModel viewModel) => CachedNetworkImage( fit: BoxFit.cover, width: double.maxFinite, + imageUrl: - getReadableUrl(viewModel.practices.first.storyImage ?? '') ?? '', + getReadableUrl( viewModel.practices.first.storyImage ?? '') ?? '', ); + + Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => Padding( padding: const EdgeInsets.only(bottom: 50), diff --git a/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart b/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart index 706ae0b..dbd6e96 100644 --- a/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart +++ b/lib/ui/views/learn_subscription/learn_subscription_viewmodel.dart @@ -82,7 +82,6 @@ class LearnSubscriptionViewModel extends FormViewModel { Future _getLearnSubscriptions() async { if (await _statusChecker.checkConnection()) { _subscriptions = await _apiService.getLearnSubscriptions(); - _subscriptions = _subscriptions + _subscriptions + _subscriptions; } } } diff --git a/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart b/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart index 9139539..3894f7f 100644 --- a/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart +++ b/lib/ui/views/learn_subscription/screens/learn_subscription_form_screen.dart @@ -6,6 +6,7 @@ import 'package:yimaru_app/ui/widgets/phone_number_prefix.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; +import '../../../widgets/learn_subscription_card.dart'; import '../../../widgets/small_app_bar.dart'; import '../../../widgets/custom_elevated_button.dart'; import '../learn_subscription_view.form.dart'; @@ -73,11 +74,15 @@ class LearnSubscriptionFormScreen ); List _buildSheetChildren(LearnSubscriptionViewModel viewModel) => [ - verticalSpaceMedium, + verticalSpaceSmall, _buildTitleWrapper(), - verticalSpaceTiny, + verticalSpaceMedium, + _buildFirstCard(), + verticalSpaceMedium, + _buildSecondCard(), + verticalSpaceLarge, _buildSubtitle(), - verticalSpaceMassive, + verticalSpaceMedium, _buildPhoneNumberWrapper(viewModel), if (viewModel.hasPhoneNumberValidationMessage && viewModel.focusPhoneNumber) @@ -87,10 +92,22 @@ class LearnSubscriptionFormScreen _buildPhoneNumberValidatorWrapper(viewModel), verticalSpaceLarge, _buildContinueButton(viewModel), - verticalSpaceMassive, + verticalSpaceMedium, _buildSecurePaymentWrapper() ]; + Widget _buildFirstCard() => const LearnSubscriptionCard( + icon: Icons.school, + title: '180+ New Lessons', + subtitle: 'Access fresh, advanced content', + ); + + Widget _buildSecondCard() => const LearnSubscriptionCard( + icon: Icons.developer_board, + title: 'Mastery Through Practice', + subtitle: 'Practice All Lessons, Modules & Levels', + ); + Widget _buildTitleWrapper() => Align( alignment: Alignment.center, child: _buildTitle(), diff --git a/lib/ui/widgets/course_tile.dart b/lib/ui/widgets/course_tile.dart deleted file mode 100644 index 27d8351..0000000 --- a/lib/ui/widgets/course_tile.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:yimaru_app/models/course_detail.dart'; -import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; - -import '../common/app_colors.dart'; -import '../common/ui_helpers.dart'; -import 'custom_elevated_button.dart'; - -class CourseTile extends StatelessWidget { - final CourseDetail courseDetail; - final GestureTapCallback? onCourseTap; - final GestureTapCallback? onPracticeTap; - - const CourseTile({ - super.key, - this.onCourseTap, - this.onPracticeTap, - required this.courseDetail, - }); - - @override - Widget build(BuildContext context) => _buildExpansionTileCard(); - - Widget _buildExpansionTileCard() => Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - border: Border.all( - color: kcPrimaryColor.withOpacity(0.2), - ), - ), - child: _buildTileStack(), - ); - - Widget _buildTileStack() => Stack( - children: [_buildExpansionTile(), _buildTileShaderState()], - ); - - Widget _buildExpansionTile() => ExpansionTile( - title: _buildTitle(), - textColor: kcDarkGrey, - showTrailingIcon: false, - initiallyExpanded: false, - collapsedIconColor: kcDarkGrey, - collapsedTextColor: kcDarkGrey, - shape: Border.all(color: kcTransparent), - expandedAlignment: Alignment.centerLeft, - backgroundColor: kcPrimaryColor.withOpacity(0.1), - controlAffinity: ListTileControlAffinity.trailing, - expandedCrossAxisAlignment: CrossAxisAlignment.start, - collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1), - childrenPadding: const EdgeInsets.symmetric(horizontal: 15), - enabled: courseDetail.courseProgress?.progressStatus == 'NOT_STARTED' - ? courseDetail.courseProgress?.displayOrder == 1 - ? true - : false - : true, - children: _buildExpansionTileChildren(), - ); - - List _buildExpansionTileChildren() => [ - _buildProgressRow(), - verticalSpaceSmall, - _buildActionButtonWrapper(), - verticalSpaceSmall - ]; - - Widget _buildTitle() => Text( - (courseDetail.course?.title == null || - (courseDetail.course?.title?.isEmpty ?? false)) - ? 'Course ${courseDetail.course?.id}' - : courseDetail.course?.title ?? '', - style: style16P600, - ); - - Widget _buildProgressRow() => Row( - mainAxisSize: MainAxisSize.min, - children: _buildProgressChildren(), - ); - - List _buildProgressChildren() => - [_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()]; - - Widget _buildProgressStatusWrapper() => Expanded( - child: _buildProgressStatus(), - ); - - Widget _buildProgressStatus() => CustomLinearProgressIndicator( - activeColor: kcPrimaryColor, - backgroundColor: kcVeryLightGrey, - progress: courseDetail.courseProgress?.progressPercentage ?? 0 / 100, - ); - - Widget _buildProgress() => Text( - '${courseDetail.courseProgress?.progressPercentage?.toInt() ?? 0}%', - style: style14DG400, - ); - - Widget _buildActionButtonWrapper() => SizedBox( - height: 40, - width: 300, - child: _buildActionButtons(), - ); - - Widget _buildActionButtons() => Row( - children: [ - _buildStartButtonWrapper(), - horizontalSpaceSmall, - _buildPracticeButtonWrapper() - ], - ); - - Widget _buildStartButtonWrapper() => Expanded( - child: _buildStartButton(), - ); - - Widget _buildStartButton() => CustomElevatedButton( - height: 15, - borderRadius: 8, - onTap: onCourseTap, - text: 'Start Course', - foregroundColor: kcWhite, - backgroundColor: kcPrimaryColor, - ); - - Widget _buildPracticeButtonWrapper() => Expanded( - child: _buildPracticeButton(), - ); - - Widget _buildPracticeButton() => CustomElevatedButton( - height: 15, - borderRadius: 8, - text: 'Practice', - onTap: onPracticeTap, - backgroundColor: kcWhite, - borderColor: kcPrimaryColor, - foregroundColor: kcPrimaryColor, - ); - - Widget _buildTileShaderState() => - courseDetail.courseProgress?.progressStatus == 'NOT_STARTED' - ? courseDetail.courseProgress?.displayOrder == 1 - ? Container() - : _buildTileShaderWrapper() - : Container(); - - Widget _buildTileShaderWrapper() => Positioned.fill( - child: _buildTileShader(), - ); - - Widget _buildTileShader() => Container( - decoration: BoxDecoration( - color: kcWhite.withOpacity(0.5), - borderRadius: BorderRadius.circular(5), - ), - ); -} diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index b246698..e882b3d 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -59,9 +59,9 @@ class LearnLessonTile extends ViewModelWidget { trailing: _buildIconState(), collapsedIconColor: kcDarkGrey, collapsedTextColor: kcDarkGrey, - leading: _buildLeadingWrapper(), shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, + leading: _buildLeadingWrapper(viewModel), enabled: (lesson.access?.isAccessible ?? false), controlAffinity: ListTileControlAffinity.trailing, expandedCrossAxisAlignment: CrossAxisAlignment.start, @@ -78,10 +78,8 @@ class LearnLessonTile extends ViewModelWidget { children: _buildExpansionTileChildren(viewModel), ); - Widget _buildLeadingWrapper() => MiniThumbnail( - thumbnail: - getReadableUrl(lesson.thumbnail ?? 'assets/images/image_1.png') ?? - 'assets/images/image_1.png'); + Widget _buildLeadingWrapper(LearnLessonViewModel viewModel) => MiniThumbnail( + thumbnail: getReadableUrl(lesson.thumbnail ?? '') ?? ''); Widget _buildTitle() => Text( lesson.title ?? '', diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index 9a629b7..201987a 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; @@ -9,6 +10,7 @@ import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart'; import '../common/app_colors.dart'; +import '../common/helper_functions.dart'; import '../common/ui_helpers.dart'; import 'custom_elevated_button.dart'; @@ -63,10 +65,10 @@ class LearnModuleTile extends ViewModelWidget { subtitle: _buildContent(), trailing: _buildLockIcon(), title: _buildTitleWrapper(), - leading: _buildIconWrapper(), collapsedIconColor: kcDarkGrey, collapsedTextColor: kcDarkGrey, backgroundColor: kcBackgroundColor, + leading: _buildIconWrapper(viewModel), shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, collapsedBackgroundColor: kcBackgroundColor, @@ -87,16 +89,25 @@ class LearnModuleTile extends ViewModelWidget { color: kcLightGrey, ); - Widget _buildIconWrapper() => CircleAvatar( + Widget _buildIconWrapper(LearnModuleViewModel viewModel) => CircleAvatar( backgroundColor: kcPrimaryColor.withOpacity(0.1), - child: _buildIcon(), + child: _buildIconClipper(viewModel), ); - Widget _buildIcon() => const Icon( - Icons.lightbulb_outline, - color: kcPrimaryColor, + Widget _buildIconClipper(LearnModuleViewModel viewModel)=> ClipRRect( + child: _buildIcon(viewModel), + ); + + Widget _buildIcon(LearnModuleViewModel viewModel) => + CachedNetworkImage( + width: 25, + height: 25, + cacheKey: viewModel.getModuleImage(module), + imageUrl: viewModel.getModuleImage(module), ); + + Widget _buildTitleWrapper() => Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: _buildTitle(),