Compare commits

..

3 Commits

Author SHA1 Message Date
36360ea7ca Merge tag '0.1.4' into develop
-fix(learn): Restructure learn hierarchy.
2026-04-25 04:23:23 +03:00
10e32149f9 Merge branch 'release/0.1.4'
-fix(learn): Restructure learn hierarchy.
2026-04-25 04:22:37 +03:00
1599bfa0ba fix(learn): Restructure learn hierarchy. 2026-04-25 04:21:04 +03:00
97 changed files with 3432 additions and 2600 deletions

View File

@ -14,11 +14,27 @@
},
"oauth_client": [
{
"client_id": "900714037062-ngc0gc426sfnnjjr494g4vni46ne5uqv.apps.googleusercontent.com",
"client_id": "900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.yimaru.lms.app",
"certificate_hash": "139ee56ac9763191d1eee882efc440c10530e6e9"
"certificate_hash": "b9cb22406df59b3b9b210896cc10fc704cc25858"
}
},
{
"client_id": "900714037062-d9aqa2eoni3ppumdpi39d5rtub2bsbbs.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.yimaru.lms.app",
"certificate_hash": "29797902ad6a24212b9d9fad71562907956f6a6c"
}
},
{
"client_id": "900714037062-ok9oeme95rfcvljtg065aj0f7mmsr0fa.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.yimaru.lms.app",
"certificate_hash": "928ead08b5e39d6a861a55ae7cceb8c402d1ee7a"
}
},
{

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedBottomsheetGenerator

View File

@ -18,8 +18,6 @@ import 'package:yimaru_app/ui/views/privacy_policy/privacy_policy_view.dart';
import 'package:yimaru_app/ui/views/terms_and_conditions/terms_and_conditions_view.dart';
import 'package:yimaru_app/ui/views/register/register_view.dart';
import 'package:yimaru_app/ui/views/login/login_view.dart';
import 'package:yimaru_app/ui/views/learn/learn_view.dart';
import 'package:yimaru_app/ui/views/learn_level/learn_level_view.dart';
import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart';
import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/api_service.dart';
@ -51,8 +49,9 @@ import 'package:yimaru_app/ui/views/course/course_view.dart';
import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/ui/views/course_practice_question/course_practice_question_view.dart';
import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dart';
import 'package:yimaru_app/ui/views/learn_submodule/learn_submodule_view.dart';
import 'package:yimaru_app/services/in_app_update_service.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/learn_course/learn_course_view.dart';
// @stacked-import
@StackedApp(
@ -73,8 +72,6 @@ import 'package:yimaru_app/ui/views/learn_submodule/learn_submodule_view.dart';
MaterialRoute(page: TermsAndConditionsView),
MaterialRoute(page: RegisterView),
MaterialRoute(page: LoginView),
MaterialRoute(page: LearnView),
MaterialRoute(page: LearnLevelView),
MaterialRoute(page: LearnModuleView),
MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView),
@ -92,8 +89,8 @@ import 'package:yimaru_app/ui/views/learn_submodule/learn_submodule_view.dart';
MaterialRoute(page: CourseSubcategoryView),
MaterialRoute(page: CourseView),
MaterialRoute(page: CoursePracticeQuestionView),
MaterialRoute(page: LearnSubcategoryView),
MaterialRoute(page: LearnSubmoduleView),
MaterialRoute(page: LearnProgramView),
MaterialRoute(page: LearnCourseView),
// @stacked-route
],
dependencies: [
@ -114,6 +111,7 @@ import 'package:yimaru_app/ui/views/learn_submodule/learn_submodule_view.dart';
LazySingleton(classType: CourseService),
LazySingleton(classType: AudioPlayerService),
LazySingleton(classType: VoiceRecorderService),
LazySingleton(classType: InAppUpdateService),
// @stacked-service
],
bottomsheets: [

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedDialogGenerator

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedLocatorGenerator
@ -19,6 +20,7 @@ import '../services/dio_service.dart';
import '../services/google_auth_service.dart';
import '../services/image_downloader_service.dart';
import '../services/image_picker_service.dart';
import '../services/in_app_update_service.dart';
import '../services/notification_service.dart';
import '../services/permission_handler_service.dart';
import '../services/secure_storage_service.dart';
@ -28,10 +30,8 @@ import '../services/voice_recorder_service.dart';
final locator = StackedLocator.instance;
Future<void> setupLocator({
String? environment,
EnvironmentFilter? environmentFilter,
}) async {
Future<void> setupLocator(
{String? environment, EnvironmentFilter? environmentFilter}) async {
// Register environments
locator.registerEnvironment(
environment: environment, environmentFilter: environmentFilter);
@ -54,4 +54,5 @@ Future<void> setupLocator({
locator.registerLazySingleton(() => CourseService());
locator.registerLazySingleton(() => AudioPlayerService());
locator.registerLazySingleton(() => VoiceRecorderService());
locator.registerLazySingleton(() => InAppUpdateService());
}

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,7 @@ class DefaultFirebaseOptions {
projectId: 'yimaru-academy-5e7e2',
storageBucket: 'yimaru-academy-5e7e2.firebasestorage.app',
androidClientId:
'900714037062-ngc0gc426sfnnjjr494g4vni46ne5uqv.apps.googleusercontent.com',
'900714037062-4trqu7ln6en4kcm6gadk0uo01qijn1mk.apps.googleusercontent.com',
iosClientId:
'900714037062-35bg0hsou56hg37mbcbpiar9uti7tcku.apps.googleusercontent.com',
iosBundleId: 'com.yimaru.lms.app',

35
lib/models/access.dart Normal file
View File

@ -0,0 +1,35 @@
import 'package:json_annotation/json_annotation.dart';
part 'access.g.dart';
@JsonSerializable()
class Access {
final String? reason;
@JsonKey(name: 'total_count')
final int? totalCount;
@JsonKey(name: 'is_completed')
final bool? isCompleted;
@JsonKey(name: 'is_accessible')
final bool? isAccessible;
@JsonKey(name: 'completed_count')
final int? completedCount;
@JsonKey(name: 'progress_percent')
final int? progressPercent;
const Access(
{this.reason,
this.totalCount,
this.isCompleted,
this.isAccessible,
this.completedCount,
this.progressPercent});
factory Access.fromJson(Map<String, dynamic> json) => _$AccessFromJson(json);
Map<String, dynamic> toJson() => _$AccessToJson(this);
}

25
lib/models/access.g.dart Normal file
View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'access.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Access _$AccessFromJson(Map<String, dynamic> json) => Access(
reason: json['reason'] as String?,
totalCount: (json['total_count'] as num?)?.toInt(),
isCompleted: json['is_completed'] as bool?,
isAccessible: json['is_accessible'] as bool?,
completedCount: (json['completed_count'] as num?)?.toInt(),
progressPercent: (json['progress_percent'] as num?)?.toInt(),
);
Map<String, dynamic> _$AccessToJson(Access instance) => <String, dynamic>{
'reason': instance.reason,
'total_count': instance.totalCount,
'is_completed': instance.isCompleted,
'is_accessible': instance.isAccessible,
'completed_count': instance.completedCount,
'progress_percent': instance.progressPercent,
};

View File

@ -0,0 +1,34 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/access.dart';
part 'learn_course.g.dart';
@JsonSerializable()
class LearnCourse {
final int? id;
final String? name;
final Access? access;
final String? description;
@JsonKey(name: 'sort_order')
final int? sortOrder;
@JsonKey(name: 'program_id')
final int? programId;
const LearnCourse(
{this.id,
this.name,
this.access,
this.programId,
this.sortOrder,
this.description});
factory LearnCourse.fromJson(Map<String, dynamic> json) =>
_$LearnCourseFromJson(json);
Map<String, dynamic> toJson() => _$LearnCourseToJson(this);
}

View File

@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learn_course.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearnCourse _$LearnCourseFromJson(Map<String, dynamic> json) => LearnCourse(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
programId: (json['program_id'] as num?)?.toInt(),
sortOrder: (json['sort_order'] as num?)?.toInt(),
description: json['description'] as String?,
);
Map<String, dynamic> _$LearnCourseToJson(LearnCourse instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'access': instance.access,
'description': instance.description,
'sort_order': instance.sortOrder,
'program_id': instance.programId,
};

View File

@ -0,0 +1,43 @@
import 'package:json_annotation/json_annotation.dart';
import 'access.dart';
part 'learn_lesson.g.dart';
@JsonSerializable()
class LearnLesson {
final int? id;
final String? title;
final Access? access;
final String? thumbnail;
final String? description;
@JsonKey(name: 'module_id')
final int? moduleId;
@JsonKey(name: 'sort_order')
final int? sortOrder;
@JsonKey(name: 'video_url')
final String? videoUrl;
const LearnLesson({
this.id,
this.title,
this.access,
this.videoUrl,
this.moduleId,
this.thumbnail,
this.sortOrder,
this.description,
});
factory LearnLesson.fromJson(Map<String, dynamic> json) =>
_$LearnLessonFromJson(json);
Map<String, dynamic> toJson() => _$LearnLessonToJson(this);
}

View File

@ -0,0 +1,32 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learn_lesson.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearnLesson _$LearnLessonFromJson(Map<String, dynamic> json) => LearnLesson(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
videoUrl: json['video_url'] as String?,
moduleId: (json['module_id'] as num?)?.toInt(),
thumbnail: json['thumbnail'] as String?,
sortOrder: (json['sort_order'] as num?)?.toInt(),
description: json['description'] as String?,
);
Map<String, dynamic> _$LearnLessonToJson(LearnLesson instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'access': instance.access,
'thumbnail': instance.thumbnail,
'description': instance.description,
'module_id': instance.moduleId,
'sort_order': instance.sortOrder,
'video_url': instance.videoUrl,
};

View File

@ -0,0 +1,41 @@
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;
final String? icon;
final String? name;
final Access? access;
final String? description;
@JsonKey(name: 'program_id')
final int? programId;
@JsonKey(name: 'course_id')
final int? courseId;
@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});
factory LearnModule.fromJson(Map<String, dynamic> json) =>
_$LearnModuleFromJson(json);
Map<String, dynamic> toJson() => _$LearnModuleToJson(this);
}

View File

@ -0,0 +1,32 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learn_module.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearnModule _$LearnModuleFromJson(Map<String, dynamic> json) => LearnModule(
id: (json['id'] as num?)?.toInt(),
icon: json['icon'] as String?,
name: json['name'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
courseId: (json['course_id'] as num?)?.toInt(),
sortOrder: (json['sort_order'] as num?)?.toInt(),
programId: (json['program_id'] as num?)?.toInt(),
description: json['description'] as String?,
);
Map<String, dynamic> _$LearnModuleToJson(LearnModule instance) =>
<String, dynamic>{
'id': instance.id,
'icon': instance.icon,
'name': instance.name,
'access': instance.access,
'description': instance.description,
'program_id': instance.programId,
'course_id': instance.courseId,
'sort_order': instance.sortOrder,
};

View File

@ -0,0 +1,43 @@
import 'package:json_annotation/json_annotation.dart';
part 'learn_practice.g.dart';
@JsonSerializable()
class LearnPractice {
final int? id;
final String? title;
@JsonKey(name: 'parent_id')
final int? parentId;
@JsonKey(name: 'quick_tips')
final String? quickTips;
@JsonKey(name: 'story_image')
final String? storyImage;
@JsonKey(name: 'parent_kind')
final String? parentKind;
@JsonKey(name: 'question_set_id')
final int? questionSetId;
@JsonKey(name: 'story_description')
final String? storyDescription;
const LearnPractice(
{this.id,
this.title,
this.parentId,
this.quickTips,
this.storyImage,
this.parentKind,
this.questionSetId,
this.storyDescription});
factory LearnPractice.fromJson(Map<String, dynamic> json) =>
_$LearnPracticeFromJson(json);
Map<String, dynamic> toJson() => _$LearnPracticeToJson(this);
}

View File

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learn_practice.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearnPractice _$LearnPracticeFromJson(Map<String, dynamic> json) =>
LearnPractice(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
parentId: (json['parent_id'] as num?)?.toInt(),
quickTips: json['quick_tips'] as String?,
storyImage: json['story_image'] as String?,
parentKind: json['parent_kind'] as String?,
questionSetId: (json['question_set_id'] as num?)?.toInt(),
storyDescription: json['story_description'] as String?,
);
Map<String, dynamic> _$LearnPracticeToJson(LearnPractice instance) =>
<String, dynamic>{
'id': instance.id,
'title': instance.title,
'parent_id': instance.parentId,
'quick_tips': instance.quickTips,
'story_image': instance.storyImage,
'parent_kind': instance.parentKind,
'question_set_id': instance.questionSetId,
'story_description': instance.storyDescription,
};

View File

@ -0,0 +1,26 @@
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;
final String? name;
final Access? access;
final String? description;
@JsonKey(name: 'sort_order')
final int? sortOrder;
const LearnProgram(
{this.id, this.name, this.access, this.sortOrder, this.description});
factory LearnProgram.fromJson(Map<String, dynamic> json) =>
_$LearnProgramFromJson(json);
Map<String, dynamic> toJson() => _$LearnProgramToJson(this);
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learn_program.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearnProgram _$LearnProgramFromJson(Map<String, dynamic> json) => LearnProgram(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String?,
access: json['access'] == null
? null
: Access.fromJson(json['access'] as Map<String, dynamic>),
sortOrder: (json['sort_order'] as num?)?.toInt(),
description: json['description'] as String?,
);
Map<String, dynamic> _$LearnProgramToJson(LearnProgram instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'access': instance.access,
'description': instance.description,
'sort_order': instance.sortOrder,
};

View File

@ -0,0 +1,56 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:yimaru_app/models/option.dart';
part 'learn_question.g.dart';
@JsonSerializable()
class LearnQuestion {
final int? id;
final int? points;
@JsonKey(name: 'set_id')
final int? setId;
@JsonKey(name: 'question_id')
final int? questionId;
@JsonKey(name: 'voice_prompt')
final String? voicePrompt;
@JsonKey(name: 'question_text')
final String? questionText;
@JsonKey(name: 'display_order')
final int? displayOrder;
@JsonKey(name: 'question_type')
final String? questionType;
@JsonKey(name: 'question_status')
final String? questionStatus;
@JsonKey(name: 'audio_correct_answer_text')
final String? audioCorrectAnswerText;
@JsonKey(name: 'sample_answer_voice_prompt')
final String? sampleAnswerVoicePrompt;
const LearnQuestion(
{this.id,
this.setId,
this.points,
this.questionId,
this.voicePrompt,
this.questionText,
this.questionType,
this.displayOrder,
this.questionStatus,
this.audioCorrectAnswerText,
this.sampleAnswerVoicePrompt});
factory LearnQuestion.fromJson(Map<String, dynamic> json) =>
_$LearnQuestionFromJson(json);
Map<String, dynamic> toJson() => _$LearnQuestionToJson(this);
}

View File

@ -1,33 +1,37 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'practice_question.dart';
part of 'learn_question.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
PracticeQuestion _$PracticeQuestionFromJson(Map<String, dynamic> json) =>
PracticeQuestion(
LearnQuestion _$LearnQuestionFromJson(Map<String, dynamic> json) =>
LearnQuestion(
id: (json['id'] as num?)?.toInt(),
tips: json['tips'] as String?,
setId: (json['set_id'] as num?)?.toInt(),
points: (json['points'] as num?)?.toInt(),
questionId: (json['question_id'] as num?)?.toInt(),
voicePrompt: json['voice_prompt'] as String?,
questionText: json['question_text'] as String?,
questionType: json['question_type'] as String?,
displayOrder: (json['display_order'] as num?)?.toInt(),
questionStatus: json['question_status'] as String?,
audioCorrectAnswerText: json['audio_correct_answer_text'] as String?,
sampleAnswerVoicePrompt: json['sample_answer_voice_prompt'] as String?,
);
Map<String, dynamic> _$PracticeQuestionToJson(PracticeQuestion instance) =>
Map<String, dynamic> _$LearnQuestionToJson(LearnQuestion instance) =>
<String, dynamic>{
'id': instance.id,
'points': instance.points,
'tips': instance.tips,
'set_id': instance.setId,
'question_id': instance.questionId,
'display_order': instance.displayOrder,
'voice_prompt': instance.voicePrompt,
'question_text': instance.questionText,
'display_order': instance.displayOrder,
'question_type': instance.questionType,
'question_status': instance.questionStatus,
'audio_correct_answer_text': instance.audioCorrectAnswerText,
'sample_answer_voice_prompt': instance.sampleAnswerVoicePrompt,
};

View File

@ -22,9 +22,15 @@ class Practice {
@JsonKey(name: 'owner_type')
final String? ownerType;
@JsonKey(name: 'intro_video_url')
final String? introVideoUrl;
@JsonKey(name: 'shuffle_questions')
final bool? shuffleQuestions;
@JsonKey(name: 'time_limit_minutes')
final int? timeLimitMinutes;
const Practice(
{this.id,
this.title,
@ -34,7 +40,9 @@ class Practice {
this.ownerId,
this.ownerType,
this.description,
this.shuffleQuestions});
this.introVideoUrl,
this.shuffleQuestions,
this.timeLimitMinutes});
factory Practice.fromJson(Map<String, dynamic> json) =>
_$PracticeFromJson(json);

View File

@ -15,7 +15,9 @@ Practice _$PracticeFromJson(Map<String, dynamic> json) => Practice(
ownerId: (json['owner_id'] as num?)?.toInt(),
ownerType: json['owner_type'] as String?,
description: json['description'] as String?,
introVideoUrl: json['intro_video_url'] as String?,
shuffleQuestions: json['shuffle_questions'] as bool?,
timeLimitMinutes: (json['time_limit_minutes'] as num?)?.toInt(),
);
Map<String, dynamic> _$PracticeToJson(Practice instance) => <String, dynamic>{
@ -27,5 +29,7 @@ Map<String, dynamic> _$PracticeToJson(Practice instance) => <String, dynamic>{
'owner_id': instance.ownerId,
'set_type': instance.setType,
'owner_type': instance.ownerType,
'intro_video_url': instance.introVideoUrl,
'shuffle_questions': instance.shuffleQuestions,
'time_limit_minutes': instance.timeLimitMinutes,
};

View File

@ -1,46 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'practice_question.g.dart';
@JsonSerializable()
class PracticeQuestion {
final int? id;
final int? points;
final String? tips;
@JsonKey(name: 'set_id')
final int? setId;
@JsonKey(name: 'question_id')
final int? questionId;
@JsonKey(name: 'display_order')
final int? displayOrder;
@JsonKey(name: 'question_text')
final String? questionText;
@JsonKey(name: 'question_type')
final String? questionType;
@JsonKey(name: 'question_status')
final String? questionStatus;
const PracticeQuestion(
{this.id,
this.tips,
this.setId,
this.points,
this.questionId,
this.questionText,
this.questionType,
this.displayOrder,
this.questionStatus});
factory PracticeQuestion.fromJson(Map<String, dynamic> json) =>
_$PracticeQuestionFromJson(json);
Map<String, dynamic> toJson() => _$PracticeQuestionToJson(this);
}

View File

@ -1,4 +1,7 @@
import 'package:dio/dio.dart';
import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/learn_program.dart';
import 'package:yimaru_app/models/level.dart';
import 'package:yimaru_app/models/question.dart';
import 'package:yimaru_app/models/subcategory.dart';
@ -7,12 +10,14 @@ import 'package:yimaru_app/models/course_lesson.dart';
import 'package:yimaru_app/models/course_progress.dart';
import 'package:yimaru_app/models/course.dart';
import 'package:yimaru_app/models/practice.dart';
import 'package:yimaru_app/models/practice_question.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';
import '../app/app.locator.dart';
import '../models/learn_course.dart';
import '../models/learn_module.dart';
import '../models/learn_question.dart';
import '../models/lesson.dart';
import '../models/module.dart';
import '../models/submodule.dart';
@ -215,7 +220,7 @@ class ApiService {
}
}
// GEt profile completion status
// Get profile completion status
Future<Map<String, dynamic>> getProfileStatus(User? user) async {
try {
Response response = await _service.dio.get(
@ -373,6 +378,201 @@ class ApiService {
}
}
// Learn learn programs
Future<List<LearnProgram>> getLearnPrograms() async {
try {
List<LearnProgram> learnPrograms = [];
final Response response =
await _service.dio.get('$kBaseUrl/api/$kApiVersionUrl/$kProgramsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['programs'] as List;
learnPrograms = decodedData.map(
(e) {
return LearnProgram.fromJson(e);
},
).toList();
return learnPrograms;
}
return [];
} catch (e) {
return [];
}
}
// Learn learn courses
Future<List<LearnCourse>> getLearnCourse(int id) async {
try {
List<LearnCourse> learnCourses = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kProgramsUrl/$id/$kCoursesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['courses'] as List;
learnCourses = decodedData.map(
(e) {
return LearnCourse.fromJson(e);
},
).toList();
return learnCourses;
}
return [];
} catch (e) {
return [];
}
}
// Learn course practices
Future<List<LearnPractice>> getLearnCoursePractices(int id) async {
try {
List<LearnPractice> practices = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kCoursesUrl/$id/$kPracticesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['practices'] as List;
practices = decodedData.map(
(e) {
return LearnPractice.fromJson(e);
},
).toList();
return practices;
}
return [];
} catch (e) {
return [];
}
}
// Get learn modules
Future<List<LearnModule>> getLearnModules(int id) async {
try {
List<LearnModule> modules = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kCoursesUrl/$id/$kModulesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['modules'] as List;
modules = decodedData.map(
(e) {
return LearnModule.fromJson(e);
},
).toList();
return modules;
}
return [];
} catch (e) {
return [];
}
}
// Learn module practices
Future<List<LearnPractice>> getLearnModulePractices(int id) async {
try {
List<LearnPractice> practices = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kModulesUrl/$id/$kPracticesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
practices = decodedData.map(
(e) {
return LearnPractice.fromJson(e);
},
).toList();
return practices;
}
return [];
} catch (e) {
return [];
}
}
// Get learn lessons
Future<List<LearnLesson>> getLearnLessons(int id) async {
try {
List<LearnLesson> lessons = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kModulesUrl/$id/$kLessonsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['lessons'] as List;
lessons = decodedData.map(
(e) {
return LearnLesson.fromJson(e);
},
).toList();
return lessons;
}
return [];
} catch (e) {
return [];
}
}
// Learn lesson practices
Future<List<LearnPractice>> getLearnLessonPractices(int id) async {
try {
List<LearnPractice> practices = [];
final Response response = await _service.dio
.get('$kBaseUrl/api/$kApiVersionUrl/$kLessonsUrl/$id/$kPracticesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['practices'] as List;
practices = decodedData.map(
(e) {
return LearnPractice.fromJson(e);
},
).toList();
return practices;
}
return [];
} catch (e) {
return [];
}
}
// Get learn questions
Future<List<LearnQuestion>> getLearnQuestions(int id) async {
try {
List<LearnQuestion> questions = [];
print('Here');
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
questions = decodedData.map(
(e) {
return LearnQuestion.fromJson(e);
},
).toList();
return questions;
}
return [];
} catch (e) {
return [];
}
}
/* TO BE MODIFIED*/
// Get categories
Future<List<Category>> getCategories() async {
try {
@ -541,9 +741,9 @@ class ApiService {
}
// Get course practic questions
Future<List<PracticeQuestion>> getCoursePracticeQuestions(int id) async {
Future<List<Question>> getCoursePracticeQuestions(int id) async {
try {
List<PracticeQuestion> coursePracticeQuestions = [];
List<Question> coursePracticeQuestions = [];
final Response response = await _service.dio
.get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions');
@ -553,7 +753,7 @@ class ApiService {
var decodedData = data['data'] as List;
coursePracticeQuestions = decodedData.map(
(e) {
return PracticeQuestion.fromJson(e);
return Question.fromJson(e);
},
).toList();
return coursePracticeQuestions;
@ -724,4 +924,52 @@ class ApiService {
return [];
}
}
// Practices
Future<List<Practice>> getPractices(int id) async {
try {
List<Practice> coursePractices = [];
final Response response = await _service.dio.get(
'$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice?owner_type=SUB_MODULE&owner_id=$id');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
coursePractices = decodedData.map(
(e) {
return Practice.fromJson(e);
},
).toList();
return coursePractices;
}
return [];
} catch (e) {
return [];
}
}
// Questions
Future<List<Question>> getQuestions(int id) async {
try {
List<Question> questions = [];
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kQuestionSetsUrl/$id/$kQuestionsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
questions = decodedData.map(
(e) {
return Question.fromJson(e);
},
).toList();
return questions;
}
return [];
} catch (e) {
return [];
}
}
}

View File

@ -21,7 +21,7 @@ class AudioPlayerService with ListenableServiceMixin {
Stream<PlayerState> get stateStream => _player.onPlayerStateChanged;
Future<void> playUrl(String url) async {
final playableUrl = getPlayableUrl(url);
final playableUrl = getReadableUrl(url);
if (playableUrl == null) {
throw Exception("Invalid audio URL");

View File

@ -0,0 +1,54 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart';
import 'package:in_app_update/in_app_update.dart';
import 'package:storage_info/storage_info.dart';
class InAppUpdateService {
Future<int> getBatteryLevel() async {
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
Future<void> checkForUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// KewedeConst().showErrorToast(
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// KewedeConst()
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// KewedeConst()
// .showErrorToast('Unable to update app, please free up space.');
}
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
}
try {
final info = await InAppUpdate.checkForUpdate();
if (info.updateAvailability == UpdateAvailability.updateAvailable) {
await InAppUpdate.completeFlexibleUpdate();
}
// ... rest of your update logic ...
} on PlatformException {
// Handle specific error code for better user experience and potentially different error messages for each issue
}
}
}

View File

@ -1,8 +1,4 @@
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter/services.dart';
import 'package:in_app_update/in_app_update.dart';
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:storage_info/storage_info.dart';
import 'package:yimaru_app/services/secure_storage_service.dart';
import '../app/app.locator.dart';
@ -16,13 +12,6 @@ class StatusCheckerService {
bool get previousConnection => _previousConnection;
// Get phone battery level
Future<int> getBatteryLevel() async {
final battery = Battery();
final batteryLevel = await battery.batteryLevel;
return batteryLevel;
}
// Check internet connection
Future<bool> checkConnection() async {
if (await InternetConnection().hasInternetAccess) {
@ -36,45 +25,4 @@ class StatusCheckerService {
return false;
}
}
// Check phone available storage
Future<int> getAvailableStorage() async {
try {
final availableStorage =
await StorageInfo().getStorageFreeSpace(SpaceUnit.Bytes);
return availableStorage.toInt(); // Convert GB to bytes
} catch (e) {
return 0;
}
}
// Check for latest update
Future<void> checkAndUpdate() async {
const requiredStorage = 500 * 1024 * 1024;
final batteryLevel =
await getBatteryLevel(); // Implement getBatteryLevel function
final int storageAvailable =
await getAvailableStorage(); // Implement getAvailableStorage
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
// 'Unable to update app, please charge your phone & free up space.');
} else if (batteryLevel < 20) {
// .showErrorToast('Unable to update app, please charge your phone.');
} else if (storageAvailable < requiredStorage) {
// .showErrorToast('Unable to update app, please free up space.');
}
return; // Prevent update from starting
}
try {
if (await checkConnection()) {
await InAppUpdate
.checkForUpdate(); // Continue update only if sufficient resources available
}
// ... rest of your update logic ...
} on PlatformException {
// Handle specific error code for better user experience and potentially different error messages for each issue
}
}
}

View File

@ -14,6 +14,12 @@ class VoiceRecorderService with ListenableServiceMixin {
WaveformRecorderController get waveController => _waveController;
bool _isRecording = false;
bool get isRecording => _isRecording;
// Start voice recording
Future<void> startRecording() async {
await _waveController.startRecording();
@ -35,4 +41,5 @@ class VoiceRecorderService with ListenableServiceMixin {
if (file == null) return null;
return file.path;
}
}

View File

@ -12,8 +12,14 @@ String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons';
String kProgramsUrl = 'programs';
String kRegisterUrl = 'register';
String kPracticesUrl = 'practices';
String kQuestionsUrl = 'questions';
String kCategoryUrl = 'categories';
String kCoursePractice = 'by-owner';
@ -34,6 +40,8 @@ String kCompleteLessonUrl = 'complete';
String kResetPassword = 'resetPassword';
String kQuestionSetsUrl = 'question-sets';
String kRequestResetCode = 'sendResetCode';
String kSubcategoriesUrl = 'sub-categories';

View File

@ -1,3 +1,6 @@
// Practice voice
enum Voice { sample, recorded }
// Response status
enum ResponseStatus { success, failure }
@ -7,6 +10,9 @@ enum LoginMethod { phone, email, google }
// Sign-up method
enum SignUpMethod { phone, email, google }
// Learn practice
enum LearnPractices { course, module, lesson }
// Voice recording state
enum VoiceRecordingState { pending, recording }
@ -16,8 +22,8 @@ enum ProficiencyLevels { a1, a2, b1, b2, none }
// Progress status
enum ProgressStatuses { pending, started, completed }
// Duolingo assessment types
enum DuolingoAssessmentType { speaking, reading, writing, listening }
// Duolingo types
enum DuolingoAssessments { speaking, reading, writing, listening }
// State object
enum StateObjects {
@ -27,17 +33,18 @@ enum StateObjects {
register,
verifyOtp,
resendOtp,
learnLevels,
learnLessons,
learnModules,
learnCourses,
profileImage,
learnPrograms,
courseLessons,
profileUpdate,
resetPassword,
subcategories,
loginWithEmail,
coursePractice,
learnPractices,
loginWithGoogle,
loadLessonVideo,
loadCourseVideo,
@ -46,7 +53,6 @@ enum StateObjects {
courseCategories,
profileCompletion,
registerWithGoogle,
learnSubcategories,
learnPracticeSample,
learnPracticeAnswer,
loginWithPhoneNumber,

View File

@ -46,7 +46,7 @@ Color getColor() {
}
// Get playable url
String? getPlayableUrl(String url) {
String? getReadableUrl(String url) {
try {
// Case 1: /file/d/FILE_ID/view
final fileIdRegex = RegExp(r'/file/d/([a-zA-Z0-9_-]+)');

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -145,7 +146,7 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setAnswerValidationMessage(String? validationMessage) =>
void setAnswerValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[AnswerValueKey] = validationMessage;
/// Clears text input fields on the Form
@ -167,7 +168,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_CoursePracticeQuestionViewTextEditingControllers[key]!.text,
_CoursePracticeQuestionViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -1,7 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/models/practice_question.dart';
import '../../../app/app.locator.dart';
import '../../../models/option.dart';
@ -43,10 +42,9 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
Question? get currentQuestion => _currentQuestion;
List<PracticeQuestion> _coursePracticeQuestions = [];
List<Question> _coursePracticeQuestions = [];
List<PracticeQuestion> get coursePracticeQuestions =>
_coursePracticeQuestions;
List<Question> get coursePracticeQuestions => _coursePracticeQuestions;
int _currentQuestionIndex = 0;
@ -184,8 +182,8 @@ class CoursePracticeQuestionViewModel extends FormViewModel {
_coursePracticeQuestions =
await _apiService.getCoursePracticeQuestions(id);
if (_coursePracticeQuestions.isNotEmpty) {
_currentQuestion = await _apiService.getCoursePracticeQuestion(
coursePracticeQuestions.first.questionId ?? 0);
_currentQuestion = await _apiService
.getCoursePracticeQuestion(coursePracticeQuestions.first.id ?? 0);
}
}
}

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -143,7 +144,7 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setAssessmentValidationMessage(String? validationMessage) =>
void setAssessmentValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[AssessmentValueKey] = validationMessage;
/// Clears text input fields on the Form
@ -165,7 +166,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_DuolingoViewTextEditingControllers[key]!.text,
_DuolingoViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -26,7 +26,7 @@ class DuolingoViewModel extends FormViewModel {
Map<String, dynamic> _selectedAssessment = {
'label': 'Speaking 01',
'intro_title': 'Speak About the Photo',
'type': DuolingoAssessmentType.speaking,
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
'intro_subtitle':
@ -40,7 +40,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Speaking 01',
'intro_title': 'Speak About the Photo',
'type': DuolingoAssessmentType.speaking,
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
'intro_subtitle':
@ -49,7 +49,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Speaking 02',
'intro_title': 'Read, Then Speak',
'type': DuolingoAssessmentType.speaking,
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': 'You will speak about the given topic',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
@ -57,7 +57,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Speaking 03',
'intro_title': 'Speaking Sample',
'type': DuolingoAssessmentType.speaking,
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': 'Youll speak for 13 minutes about a given topic.',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
@ -65,7 +65,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Speaking 04',
'intro_title': 'Interactive Speaking',
'type': DuolingoAssessmentType.speaking,
'type': DuolingoAssessments.speaking,
'outro_title': 'Speaking Practice Completed',
'intro_subtitle': ' Youll answer a series of short questions.',
'outro_subtitle': 'Youve finished this speaking session. Great work!',
@ -73,7 +73,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Writing 05',
'intro_title': 'Write About the Photo',
'type': DuolingoAssessmentType.writing,
'type': DuolingoAssessments.writing,
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
@ -82,7 +82,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Writing 06',
'intro_title': 'Writing Sample',
'type': DuolingoAssessmentType.writing,
'type': DuolingoAssessments.writing,
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
'intro_subtitle':
@ -90,7 +90,7 @@ class DuolingoViewModel extends FormViewModel {
},
{
'label': 'Writing 07',
'type': DuolingoAssessmentType.writing,
'type': DuolingoAssessments.writing,
'outro_title': 'Writing Practice Completed',
'intro_title': 'Interactive Writing Part 1',
'outro_subtitle': 'Youve finished this writing session. Great work!',
@ -99,7 +99,7 @@ class DuolingoViewModel extends FormViewModel {
},
{
'label': 'Writing 08',
'type': DuolingoAssessmentType.writing,
'type': DuolingoAssessments.writing,
'intro_title': 'Interactive Writing Part 2',
'outro_title': 'Writing Practice Completed',
'outro_subtitle': 'Youve finished this writing session. Great work!',
@ -109,7 +109,7 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Listening 09',
'intro_title': 'Listen and Type',
'type': DuolingoAssessmentType.listening,
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_subtitle':
'You will hear a short audio clip. Type exactly what you hear.',
@ -117,7 +117,7 @@ class DuolingoViewModel extends FormViewModel {
},
{
'label': 'Listening 10',
'type': DuolingoAssessmentType.listening,
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_title': 'Interactive Listening - Part 1',
'intro_subtitle': ' Listen carefully and complete the missing words.',
@ -125,7 +125,7 @@ class DuolingoViewModel extends FormViewModel {
},
{
'label': 'Listening 11',
'type': DuolingoAssessmentType.listening,
'type': DuolingoAssessments.listening,
'outro_title': 'Listening Practice Completed',
'intro_title': 'Interactive Listening - Part 2',
'intro_subtitle': 'Listen and choose the correct option.',
@ -133,7 +133,7 @@ class DuolingoViewModel extends FormViewModel {
},
{
'label': 'Assessment 12',
'type': DuolingoAssessmentType.listening,
'type': DuolingoAssessments.listening,
'title': 'Interactive Listening - Part 3',
'outro_title': 'Listening Practice Completed',
'subtitle': 'Write a summary of the conversation you just had',
@ -142,14 +142,14 @@ class DuolingoViewModel extends FormViewModel {
{
'label': 'Reading 13',
'intro_title': 'Read and Select',
'type': DuolingoAssessmentType.reading,
'type': DuolingoAssessments.reading,
'intro_subtitle':
'Read the sentence and select the option that correctly completes the meaning.'
},
{
'label': 'Reading 14',
'intro_title': 'Fill in the blank',
'type': DuolingoAssessmentType.reading,
'type': DuolingoAssessments.reading,
'intro_subtitle': 'Complete the sentences by filling in the missing words'
},
];

View File

@ -13,7 +13,7 @@ import '../../../widgets/wave_wrapper.dart';
class DuolingoIntroScreen extends ViewModelWidget<DuolingoViewModel> {
final String title;
final String subtitle;
final DuolingoAssessmentType type;
final DuolingoAssessments type;
const DuolingoIntroScreen(
{super.key,
@ -22,11 +22,11 @@ class DuolingoIntroScreen extends ViewModelWidget<DuolingoViewModel> {
required this.subtitle});
IconData _getIcon() {
if (type == DuolingoAssessmentType.speaking) {
if (type == DuolingoAssessments.speaking) {
return Icons.waves;
} else if (type == DuolingoAssessmentType.writing) {
} else if (type == DuolingoAssessments.writing) {
return Iconsax.pen_add;
} else if (type == DuolingoAssessmentType.listening) {
} else if (type == DuolingoAssessments.listening) {
return Icons.hearing;
} else {
return Icons.book;

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -230,13 +231,13 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setPasswordValidationMessage(String? validationMessage) =>
void setPasswordValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
setResetCodeValidationMessage(String? validationMessage) =>
void setResetCodeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ResetCodeValueKey] = validationMessage;
setEmailValidationMessage(String? validationMessage) =>
void setEmailValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
setConfirmPasswordValidationMessage(String? validationMessage) =>
void setConfirmPasswordValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ConfirmPasswordValueKey] =
validationMessage;
@ -265,7 +266,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_ForgetPasswordViewTextEditingControllers[key]!.text,
_ForgetPasswordViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -3,9 +3,10 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart';
import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dart';
import 'package:yimaru_app/ui/views/learn_program/learn_program_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart';
import 'package:yimaru_app/ui/widgets/coming_soon.dart';
import 'home_viewmodel.dart';
@ -18,7 +19,6 @@ class HomeView extends StackedView<HomeViewModel> {
@override
void onViewModelReady(HomeViewModel viewModel) async {
// Removable
print('HERE');
await _init(viewModel);
super.onViewModelReady(viewModel);
}
@ -83,9 +83,9 @@ Widget _buildProfileIcon() => const Icon(Icons.person);
Widget getViewForIndex(int index) {
switch (index) {
case 0:
return const LearnSubcategoryView();
return const LearnProgramView();
case 1:
return const CourseCategoryView();
return const ComingSoon();
default:
return const ProfileView();

View File

@ -1,99 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/learn_tile.dart';
import '../../../models/course.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/small_app_bar.dart';
import 'learn_viewmodel.dart';
class LearnView extends StackedView<LearnViewModel> {
final int id;
const LearnView({Key? key, required this.id}) : super(key: key);
@override
void onViewModelReady(LearnViewModel viewModel) async {
await viewModel.getCourses(id);
super.onViewModelReady(viewModel);
}
@override
LearnViewModel viewModelBuilder(BuildContext context) => LearnViewModel();
@override
Widget builder(
BuildContext context,
LearnViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildLearnColumnWrapper(viewModel)
],
);
Widget _buildAppBar(LearnViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop,
showBackButton: true,
);
Widget _buildLearnColumnWrapper(LearnViewModel viewModel) =>
Expanded(child: _buildLearnColumnScrollView(viewModel));
Widget _buildLearnColumnScrollView(LearnViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(LearnViewModel viewModel) =>
viewModel.busy(StateObjects.learnCourses)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.courses.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildTile(
course: viewModel.courses[index],
onTap: () async => await viewModel
.navigateToLearnLevel(viewModel.courses[index].id ?? 0),
),
);
Widget _buildTile({
required Course course,
required GestureTapCallback onTap,
}) =>
LearnTile(
onTap: onTap,
course: course,
);
}

View File

@ -1,43 +0,0 @@
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/course.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
class LearnViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn courses
List<Course> _courses = [];
List<Course> get courses => _courses;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnLevel(int id) async =>
_navigationService.navigateToLearnLevelView(id: id);
// Remote api call
// Learn courses
Future<void> getCourses(int id) async => await runBusyFuture(_getCourses(id),
busyObject: StateObjects.learnCourses);
Future<void> _getCourses(int id) async {
if (_courses.isEmpty) {
if (await _statusChecker.checkConnection()) {
_courses = await _apiService.getCourses(id);
}
}
}
}

View File

@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_course.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/learn_course_tile.dart';
import '../../widgets/small_app_bar.dart';
import 'learn_course_viewmodel.dart';
class LearnCourseView extends StackedView<LearnCourseViewModel> {
final int id;
const LearnCourseView({Key? key, required this.id}) : super(key: key);
@override
void onViewModelReady(LearnCourseViewModel viewModel) async {
await viewModel.getLearnCourses(id);
super.onViewModelReady(viewModel);
}
@override
LearnCourseViewModel viewModelBuilder(BuildContext context) =>
LearnCourseViewModel();
@override
Widget builder(
BuildContext context,
LearnCourseViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnCourseViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnCourseViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnCourseViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnCourseViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildCoursesColumnWrapper(viewModel)
],
);
Widget _buildAppBar(LearnCourseViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop,
showBackButton: true,
);
Widget _buildCoursesColumnWrapper(LearnCourseViewModel viewModel) =>
Expanded(child: _buildLevelsColumnScrollView(viewModel));
Widget _buildLevelsColumnScrollView(LearnCourseViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(LearnCourseViewModel viewModel) =>
viewModel.busy(StateObjects.learnCourses)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnCourseViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.learnCourses.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
course: viewModel.learnCourses[index],
onViewTap: () async => await viewModel
.navigateToLearnModule(viewModel.learnCourses[index]),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.learnCourses[index].id ?? 0),
),
separatorBuilder: (context, index) => verticalSpaceSmall,
);
Widget _buildTile({
required LearnCourse course,
required GestureTapCallback onViewTap,
required GestureTapCallback onPracticeTap,
}) =>
LearnCourseTile(
course: course,
onViewTap: onViewTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -0,0 +1,49 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart';
import '../../../models/learn_course.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class LearnCourseViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn courses
List<LearnCourse> _learnCourses = [];
List<LearnCourse> get learnCourses => _learnCourses;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course);
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.course);
// Remote api call
// Learn courses
Future<void> getLearnCourses(int id) async =>
await runBusyFuture(_getLearnCourses(id),
busyObject: StateObjects.learnCourses);
Future<void> _getLearnCourses(int id) async {
if (_learnCourses.isEmpty) {
if (await _statusChecker.checkConnection()) {
_learnCourses = await _apiService.getLearnCourse(id);
_learnCourses
.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
}
}
}
}

View File

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/submodule.dart';
import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/models/learn_module.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/widgets/learn_lesson_tile.dart';
import 'package:yimaru_app/ui/widgets/module_progress.dart';
import 'package:yimaru_app/ui/widgets/motivation_card.dart';
import '../../../models/lesson.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
@ -14,18 +14,16 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_viewmodel.dart';
class LearnLessonView extends StackedView<LearnLessonViewModel> {
final Submodule submodule;
const LearnLessonView({Key? key, required this.submodule}) : super(key: key);
final LearnModule module;
const LearnLessonView({Key? key, required this.module}) : super(key: key);
@override
void onViewModelReady(LearnLessonViewModel viewModel) async {
await viewModel.getLessons(submodule.id ?? 0);
await viewModel.getLessons(module.id ?? 0);
super.onViewModelReady(viewModel);
}
Widget getPadding(context) {
double half = screenHeight(context) / 2;
return SizedBox(
@ -116,10 +114,8 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
verticalSpaceTiny,
_buildSubtitle(),
verticalSpaceSmall,
verticalSpaceSmall,
_buildModuleProgress(),
verticalSpaceMedium,
verticalSpaceMedium,
_buildMotivationCard(),
verticalSpaceMedium,
_buildHeader(),
@ -128,13 +124,13 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
];
Widget _buildTitle() => Text(
submodule.title ?? '',
module.name ?? '',
style: style16DG600,
);
Widget _buildSubtitle() => Text(
submodule.description ?? '',
style: style14DG600,
module.description ?? '',
style: style14DG500,
);
Widget _buildModuleProgress() => const ModuleProgress();
@ -146,16 +142,14 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
style: style18DG700,
);
Widget _buildListViewBuilder(LearnLessonViewModel viewModel) =>
viewModel.busy(StateObjects.learnLessons)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder(
shrinkWrap: true,
@ -163,16 +157,21 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
lesson: viewModel.lessons[index],
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(viewModel.lessons[index]),
onLessonTap: () async => await viewModel
.navigateToLearnLessonDetail(viewModel.lessons[index]),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.lessons[index].id ?? 0),
),
);
Widget _buildTile({
required Lesson lesson,
required LearnLesson lesson,
required GestureTapCallback? onLessonTap,
required GestureTapCallback? onPracticeTap,
}) =>
LearnLessonTile(
lesson: lesson,
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -1,10 +1,10 @@
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_lesson.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../models/lesson.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
@ -17,28 +17,30 @@ class LearnLessonViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>();
// Learn lessons
List<Lesson> _lessons = [];
List<LearnLesson> _lessons = [];
List<Lesson> get lessons => _lessons;
List<LearnLesson> get lessons => _lessons;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnLessonDetail(Lesson lesson) async =>
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson);
Future<void> navigateToLearnLessonDetail(LearnLesson lesson) async =>
await _navigationService.navigateToLearnLessonDetailView(lesson: lesson);
// Remote api call
// Learn modules
// Learn lessons
Future<void> getLessons(int id) async => await runBusyFuture(_getLessons(id),
busyObject: StateObjects.learnLessons);
Future<void> _getLessons(int id) async {
if (_lessons.isEmpty) {
if (await _statusChecker.checkConnection()) {
_lessons = await _apiService.getLessons(id);
_lessons.sort(
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
_lessons = await _apiService.getLearnLessons(id);
_lessons.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
}
}
}

View File

@ -1,30 +1,27 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:vimeo_video_player/vimeo_video_player.dart';
import 'package:yimaru_app/ui/widgets/empty_video_player.dart';
import 'package:yimaru_app/models/learn_lesson.dart';
import '../../../models/lesson.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_elevated_button.dart';
import '../../widgets/small_app_bar.dart';
import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
final Lesson lesson;
final LearnLesson lesson;
const LearnLessonDetailView({Key? key, required this.lesson})
: super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
await viewModel.pause();
// await viewModel.navigateToLearnPractice(practices);
await viewModel.navigateToLearnPractice();
}
@override
void onDispose(LearnLessonDetailViewModel viewModel) {
print('DISPOSED');
viewModel.close();
super.onDispose(viewModel);
}
@ -126,8 +123,6 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
child: _buildVideoPlayer(viewModel),
);
Widget _buildVideoPlayer(LearnLessonDetailViewModel viewModel) =>
_buildVimeoPlayer(viewModel);
@ -136,11 +131,9 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
isAutoPlay: true,
onInAppWebViewCreated: (controller) =>
viewModel.initializePlayer(controller),
videoId: lesson.teachingVideoUrl?.split('/').last ?? '',
videoId: lesson.videoUrl?.split('/').last ?? '',
);
Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer();
Widget _buildDescriptionWrapper() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildDescription(),
@ -148,7 +141,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Widget _buildDescription() => Text(
lesson.description ?? '',
style: style14DG600,
style: style14DG400,
);
Widget _buildContinueButtonWrapper(LearnLessonDetailViewModel viewModel) =>
@ -164,8 +157,8 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Lessons',
borderRadius: 12,
text: 'Practices',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel),

View File

@ -1,8 +1,6 @@
import 'package:chewie/chewie.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:video_player/video_player.dart';
import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
@ -10,34 +8,26 @@ import '../../../app/app.locator.dart';
import '../../../services/status_checker_service.dart';
class LearnLessonDetailViewModel extends BaseViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Video player config
ChewieController? _chewieController;
ChewieController? get chewieController => _chewieController;
InAppWebViewController? _webViewController;
InAppWebViewController? get webViewController => _webViewController;
VideoPlayerController? _videoPlayerController;
VideoPlayerController? get videoPlayerController => _videoPlayerController;
// Video player
void close() {
_videoPlayerController?.dispose();
_chewieController?.dispose();
webViewController?.dispose();
}
Future<void> pause() async {
await _chewieController?.pause();
await webViewController?.pause();
}
void initializePlayer(InAppWebViewController controller){
void initializePlayer(InAppWebViewController controller) {
_webViewController = controller;
rebuildUi();
}
@ -52,17 +42,9 @@ class LearnLessonDetailViewModel extends BaseViewModel {
rebuildUi();
}
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(
List<Map<String, dynamic>> practices) async =>
await _navigationService.navigateToLearnPracticeView(
practices: practices,
buttonLabel: 'Start Practice',
title: 'Let \'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
Future<void> navigateToLearnPractice() async {}
// await _navigationService.navigateToLearnPracticeView();
}

View File

@ -1,100 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/learn_level_tile.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
import '../../../models/level.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import 'learn_level_viewmodel.dart';
class LearnLevelView extends StackedView<LearnLevelViewModel> {
final int id;
const LearnLevelView({Key? key, required this.id}) : super(key: key);
@override
void onViewModelReady(LearnLevelViewModel viewModel) async {
await viewModel.getLevels(id);
super.onViewModelReady(viewModel);
}
@override
LearnLevelViewModel viewModelBuilder(BuildContext context) =>
LearnLevelViewModel();
@override
Widget builder(
BuildContext context,
LearnLevelViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnLevelViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnLevelViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnLevelViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnLevelViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildLevelsColumnWrapper(viewModel)
],
);
Widget _buildAppBar(LearnLevelViewModel viewModel) => SmallAppBar(
title: 'Levels',
onTap: viewModel.pop,
showBackButton: true,
);
Widget _buildLevelsColumnWrapper(LearnLevelViewModel viewModel) =>
Expanded(child: _buildLevelsColumnScrollView(viewModel));
Widget _buildLevelsColumnScrollView(LearnLevelViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(LearnLevelViewModel viewModel) =>
viewModel.busy(StateObjects.learnLevels)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnLevelViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.levels.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
level: viewModel.levels[index],
onTap: () async =>
await viewModel.navigateToModule(viewModel.levels[index]),
),
separatorBuilder: (context, index) => verticalSpaceSmall,
);
Widget _buildTile({
required Level level,
required GestureTapCallback onTap,
}) =>
LearnLevelTile(
onTap: onTap,
level: level,
);
}

View File

@ -1,45 +0,0 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart';
import '../../../models/level.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class LearnLevelViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn levels
List<Level> _levels = [];
List<Level> get levels => _levels;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToModule(Level level) async =>
_navigationService.navigateToLearnModuleView(level: level);
// Remote api call
// Learn levels
Future<void> getLevels(int id) async =>
await runBusyFuture(_getLevels(id), busyObject: StateObjects.learnLevels);
Future<void> _getLevels(int id) async {
if (_levels.isEmpty) {
if (await _statusChecker.checkConnection()) {
_levels = await _apiService.getLevels(id);
_levels.sort(
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
}
}
}
}

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/level.dart';
import 'package:yimaru_app/models/learn_module.dart';
import 'package:yimaru_app/ui/widgets/learn_module_tile.dart';
import 'package:yimaru_app/ui/widgets/overall_learn_progress.dart';
import '../../../models/module.dart';
import '../../../models/learn_course.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
@ -13,13 +13,13 @@ import '../../widgets/small_app_bar.dart';
import 'learn_module_viewmodel.dart';
class LearnModuleView extends StackedView<LearnModuleViewModel> {
final Level level;
final LearnCourse course;
const LearnModuleView({Key? key, required this.level}) : super(key: key);
const LearnModuleView({Key? key, required this.course}) : super(key: key);
@override
void onViewModelReady(LearnModuleViewModel viewModel) async {
await viewModel.getModules(level.id ?? 0);
await viewModel.getLearnModules(1);
super.onViewModelReady(viewModel);
}
@ -58,7 +58,6 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
);
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
title: 'Modules',
onTap: viewModel.pop,
showBackButton: true,
);
@ -78,27 +77,28 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
);
List<Widget> _buildLevelsColumnChildren(LearnModuleViewModel viewModel) => [
verticalSpaceMedium,
verticalSpaceSmall,
_buildTitle(),
_buildSubtitle(),
verticalSpaceLarge,
verticalSpaceMedium,
_buildOverallProgress(),
verticalSpaceMedium,
_buildListViewBuilder(viewModel)
];
Widget _buildTitle() => Text(
level.title ?? '',
course.name ?? '',
style: style18P600,
);
Widget _buildSubtitle() => Text(
'Your Current Level',
style: style14DG400,
style: style14P400,
);
Widget _buildOverallProgress() => OverallLearnProgress(
color: kcPrimaryColor.withOpacity(0.1),
indicatorBackgroundColor: kcWhite,
backgroundColor: kcPrimaryColor.withOpacity(0.1),
);
Widget _buildListViewBuilder(LearnModuleViewModel viewModel) =>
@ -116,17 +116,20 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index],
onModuleTap: () async => await viewModel
.navigateToLearnSubmodule(viewModel.modules[index]),
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.modules[index].id ?? 0),
),
);
Widget _buildTile({
required Module module,
required LearnModule module,
required GestureTapCallback onModuleTap,
required GestureTapCallback onPracticeTap,
}) =>
LearnModuleTile(
module: module,
onModuleTap: onModuleTap,
);
module: module,
onModuleTap: onModuleTap,
onPracticeTap: onPracticeTap);
}

View File

@ -1,7 +1,7 @@
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/module.dart';
import 'package:yimaru_app/models/learn_module.dart';
import '../../../app/app.locator.dart';
import '../../../services/api_service.dart';
@ -17,28 +17,31 @@ class LearnModuleViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>();
// Learn module
List<Module> _modules = [];
List<LearnModule> _modules = [];
List<Module> get modules => _modules;
List<LearnModule> get modules => _modules;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnSubmodule(Module module) async =>
await _navigationService.navigateToLearnSubmoduleView(module: module);
Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module);
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.module);
// Remote api call
// Learn modules
Future<void> getModules(int id) async => await runBusyFuture(_getModules(id),
busyObject: StateObjects.learnModules);
Future<void> getLearnModules(int id) async =>
await runBusyFuture(_getLearnModules(id),
busyObject: StateObjects.learnModules);
Future<void> _getModules(int id) async {
Future<void> _getLearnModules(int id) async {
if (_modules.isEmpty) {
if (await _statusChecker.checkConnection()) {
_modules = await _apiService.getModules(id);
_modules.sort(
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
_modules = await _apiService.getLearnModules(id);
_modules.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
}
}
}

View File

@ -1,47 +1,30 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/finish_learn_practice_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practices_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_questions_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/interact_learn_practice_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_intro_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import '../../common/app_colors.dart';
import '../../widgets/cancel_learn_practice_sheet.dart';
import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
final String title;
final String subtitle;
final String buttonLabel;
final List<Map<String, dynamic>> practices;
final int id;
final LearnPractices practice;
const LearnPracticeView(
{Key? key,
required this.title,
required this.subtitle,
required this.practices,
required this.buttonLabel})
const LearnPracticeView({Key? key, required this.id, required this.practice})
: super(key: key);
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop();
viewModel.goTo(0);
viewModel.stopRecording();
}
Future<void> _pop(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) async {
{
if (viewModel.currentPage == 3) {
await _showSheet(context: context, viewModel: viewModel);
} else {
viewModel.goBack();
}
}
viewModel.pop();
}
Future<void> _showSheet(
@ -54,6 +37,12 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
builder: (_) => _buildSheet(viewModel),
);
@override
void onViewModelReady(LearnPracticeViewModel viewModel) async {
await viewModel.getLearnPractices(id: id, practice: practice);
super.onViewModelReady(viewModel);
}
@override
LearnPracticeViewModel viewModelBuilder(BuildContext context) =>
LearnPracticeViewModel();
@ -70,50 +59,45 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
{required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
PopScope(
canPop: viewModel.currentPage == 0 ? true : false,
onPopInvokedWithResult: (value, data) async =>
await _pop(context: context, viewModel: viewModel),
canPop: false,
onPopInvokedWithResult: (value, data)
async=>await _showSheet(context: context, viewModel: viewModel),
child: _buildScaffoldWrapper(viewModel));
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildBody(viewModel),
body: _buildBodyState(viewModel),
);
Widget _buildBody(LearnPracticeViewModel viewModel) =>
IndexedStack(index: viewModel.currentPage, children: _buildScreens());
Widget _buildBodyState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.learnPractices)
? const PageLoadingIndicator()
: _buildBody(viewModel);
List<Widget> _buildScreens() => [
_buildLearnPracticesScreen(),
_buildLearnPracticeIntroScreen(),
_buildStartLearnPracticeScreen(),
_buildInteractLearnPracticeScreen(),
Widget _buildBody(LearnPracticeViewModel viewModel) => IndexedStack(
index: viewModel.currentPage, children: _buildScreens(viewModel));
List<Widget> _buildScreens(LearnPracticeViewModel viewModel) => [
_buildLearnPracticeIntroScreen(viewModel),
_buildLearnPracticeQuestionsScreen(viewModel),
_buildFinishLearnPracticeScreen(),
_buildLearnPracticeResultScreen(),
_buildLearnPracticeCompletionScreen()
];
Widget _buildLearnPracticesScreen() => LearnPracticesScreen(
practices: practices,
);
Widget _buildLearnPracticeIntroScreen(LearnPracticeViewModel viewModel) =>
const LearnPracticeIntroScreen();
Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen(
title: title,
subtitle: subtitle,
buttonLabel: buttonLabel,
);
Widget _buildStartLearnPracticeScreen() => const StartLearnPracticeScreen();
Widget _buildInteractLearnPracticeScreen() =>
const InteractLearnPracticeScreen();
Widget _buildLearnPracticeQuestionsScreen(LearnPracticeViewModel viewModel) =>
const LearnPracticeQuestionsScreen();
Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen();

View File

@ -1,20 +1,30 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/cupertino.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:waveform_recorder/waveform_recorder.dart';
import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/models/user.dart';
import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart';
import '../../../models/learn_question.dart';
import '../../../models/question.dart';
import '../../../services/api_service.dart';
import '../../../services/audio_player_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/app_colors.dart';
class LearnPracticeViewModel extends ReactiveViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _dialogService = locator<DialogService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _audioPlayerService = locator<AudioPlayerService>();
@ -67,23 +77,48 @@ class LearnPracticeViewModel extends ReactiveViewModel {
VoiceRecordingState get recordingState => _recordingState;
// Busy object
StateObjects _busyObject = StateObjects.none;
String? _busyObject;
StateObjects get busyObject => _busyObject;
String? get busyObject => _busyObject;
Voice? _playing;
Voice? get playing =>_playing;
// Learn practices
List<LearnPractice> _practices = [];
List<LearnPractice> get practices => _practices;
// Practice questions
List<LearnQuestion> _questions = [];
List<LearnQuestion> get questions => _questions;
// Practice answers
List<Map<String, dynamic>> _answers = [];
List<Map<String, dynamic>> get answers => _answers;
// In-app navigation
int _currentPage = 0;
int get currentPage => _currentPage;
// Practice
Map<String, dynamic> _selectedPractice = {};
final PageController _questionSetController = PageController();
Map<String, dynamic> get selectedPractice => _selectedPractice;
PageController get questionSetController => _questionSetController;
final PageController _questionController = PageController();
PageController get questionController => _questionController;
// Voice recorder
Future<void> stopRecording() async =>
Future<void> stopRecording() async {
if (_voiceRecorderService.waveController.isRecording) {
await _voiceRecorderService.stopRecording();
}
}
Future<void> startRecording() async => await runBusyFuture(_startRecording(),
busyObject: StateObjects.recordLearnPracticeAnswer);
@ -91,24 +126,6 @@ class LearnPracticeViewModel extends ReactiveViewModel {
Future<void> _startRecording() async =>
await _voiceRecorderService.startRecording();
// Sample audio
Future<void> playSampleAudio() async =>
await runBusyFuture(_playSampleAudio(),
busyObject: StateObjects.learnPracticeSample);
Future<void> _playSampleAudio() async {
setBusyObject(StateObjects.learnPracticeSample);
await _audioPlayerService.playUrl(_selectedPractice['sample_answer']);
}
Future<void> pauseSampleAudio() async =>
await runBusyFuture(_pauseSampleAudio(),
busyObject: StateObjects.learnPracticeSample);
Future<void> _pauseSampleAudio() async {
setBusyObject(StateObjects.learnPracticeSample);
await _audioPlayerService.pause();
}
// Play practice audio
void _listenToAudio() {
@ -125,46 +142,53 @@ class LearnPracticeViewModel extends ReactiveViewModel {
});
}
Future<void> playQuestionAudio() async =>
await runBusyFuture(_playQuestionAudio(),
Future<void> playVoicePrompt(LearnQuestion question) async =>
await runBusyFuture(_playVoicePrompt(question),
busyObject: StateObjects.learnPracticeQuestion);
Future<void> _playQuestionAudio() async {
goTo(3);
await _audioPlayerService.playUrl(_selectedPractice['question_audio_url']);
Future<void> _playVoicePrompt(LearnQuestion question) async {
_questionController.jumpToPage(1);
await _audioPlayerService.playUrl(question.voicePrompt ?? '');
}
// Recorded audio
Future<void> playRecordedAudio() async =>
await runBusyFuture(_playRecordedAudio(),
busyObject: StateObjects.learnPracticeAnswer);
Future<void> _playRecordedAudio() async {
setBusyObject(StateObjects.learnPracticeAnswer);
await _audioPlayerService
.playLocal(await _voiceRecorderService.getRecordedAudio() ?? '');
Future<void> playResult({required Map<String,dynamic> answer,required Voice voice})async{
setBusyObject(
playing: voice,
object: answer['busy_object']);
await playAudio(voice: voice,answer: answer);
}
Future<void> pauseRecordedAudio() async =>
await runBusyFuture(_pauseRecordedAudio(),
busyObject: StateObjects.learnPracticeAnswer);
Future<void> playAudio({required Map<String,dynamic> answer,required Voice voice}) async =>
await runBusyFuture(_playAudio(voice:voice,answer:answer),
busyObject: answer['busy_object']);
Future<void> _pauseRecordedAudio() async {
setBusyObject(StateObjects.learnPracticeAnswer);
Future<void> _playAudio({required Map<String,dynamic> answer,required Voice voice}) async {
if(voice == Voice.recorded){
await _audioPlayerService
.playLocal(answer['recorded_voice_answer']);
}else{
await _audioPlayerService.playUrl(answer['sample_voice_answer']);
}
}
Future<void> pauseAudio() async {
await _audioPlayerService.pause();
}
// Set busy object
void setBusyObject(StateObjects object) {
void setBusyObject({required String object,required Voice playing}) {
_playing = playing;
_busyObject = object;
notifyListeners();
rebuildUi();
}
// Practice
void setPractice(Map<String, dynamic> practice) {
_selectedPractice = practice;
goTo(1);
}
// Dialogue
Future<bool?> showAbortDialog() async {
@ -195,6 +219,53 @@ class LearnPracticeViewModel extends ReactiveViewModel {
}
}
Future<void> nextQuestion({required int index,required LearnQuestion question}) async {
await stopRecording();
_answers.add({
'busy_object': question.id.toString(),
'sample_text_answer': question.audioCorrectAnswerText,
'sample_voice_answer': question.sampleAnswerVoicePrompt,
'recorded_voice_answer' : _voiceRecorderService.getRecordedAudio() ,
});
if (index != _questions.length) {
_questionSetController.nextPage(
duration: const Duration(milliseconds: 350),
curve: Curves.easeInOutCubic);
} else {
goTo(3);
}
}
// Navigation
void pop() => _navigationService.back();
// Remote api call
// Learn practice
Future<void> getLearnPractices(
{required int id, required LearnPractices practice}) async =>
await runBusyFuture(_getLearnPractices(id: id, practice: practice),
busyObject: StateObjects.learnPractices);
Future<void> _getLearnPractices(
{required int id, required LearnPractices practice}) async {
if (await _statusChecker.checkConnection()) {
if (practice == LearnPractices.course) {
_practices = await _apiService.getLearnCoursePractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else if (practice == LearnPractices.module) {
_practices = await _apiService.getLearnModulePractices(id);
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
} else {
_practices = await _apiService.getLearnLessonPractices(id);
print('PRACTICE LENGTH: ${_practices.length}');
await _getLearnPracticeQuestions(_practices.first.questionSetId ?? 0);
}
}
}
Future<void> _getLearnPracticeQuestions(int id) async {
_questions = await _apiService.getLearnQuestions(id);
}
}

View File

@ -9,6 +9,7 @@ import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart';
import 'package:yimaru_app/ui/widgets/wave_wrapper.dart';
import '../../../../models/learn_question.dart';
import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart';
@ -17,22 +18,24 @@ import '../../../widgets/small_app_bar.dart';
class InteractLearnPracticeScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const InteractLearnPracticeScreen({super.key});
final int index;
final LearnQuestion question;
const InteractLearnPracticeScreen(
{super.key, required this.index, required this.question});
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop();
viewModel.goTo(0);
viewModel.pop();
viewModel.stopRecording();
}
void _start(LearnPracticeViewModel viewModel) {
viewModel.playQuestionAudio();
}
void _start(LearnPracticeViewModel viewModel) =>
viewModel.playVoicePrompt(question);
Future<void> _stop(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording();
viewModel.goTo(4);
}
Future<void> _stop(LearnPracticeViewModel viewModel) async =>
await viewModel.nextQuestion(index: index,question: question);
Future<void> _showSheet(
{required BuildContext context,
@ -41,7 +44,7 @@ class InteractLearnPracticeScreen
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel),
builder: (cxt) => _buildSheet(viewModel),
);
@override
@ -68,17 +71,10 @@ class InteractLearnPracticeScreen
children: [
_buildBodyColumnWrapper(context: context, viewModel: viewModel),
_buildProgressIndicatorState(viewModel),
_buildSpeakerState(context: context, viewModel: viewModel)
_buildPageLoadingIndicatorState(viewModel)
],
);
Widget _buildSpeakerState(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
viewModel.busy(StateObjects.learnPracticeQuestion)
? const PageLoadingIndicator()
: Container();
Widget _buildBodyColumnWrapper(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
@ -100,24 +96,25 @@ class InteractLearnPracticeScreen
{required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
[
_buildAppBarWrapper(viewModel),
_buildAppBarWrapper(context: context,viewModel: viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildLowerButtonsSectionWrapper(context: context, viewModel: viewModel)
];
Widget _buildAppBarWrapper(LearnPracticeViewModel viewModel) => Column(
Widget _buildAppBarWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
_buildAppBar(context: context,viewModel: viewModel),
verticalSpaceMedium,
],
);
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
Widget _buildAppBar( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => SmallAppBar(
showBackButton: true,
title: 'Practice Speaking',
onTap: () async => await _cancel(viewModel),
);
onTap: () async => await _showSheet(context: context,viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})');
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
Column(
@ -328,6 +325,7 @@ class InteractLearnPracticeScreen
CancelLearnPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
@ -355,4 +353,9 @@ class InteractLearnPracticeScreen
activeColor: kcPrimaryColor,
progress: viewModel.progress,
backgroundColor: kcVeryLightGrey);
Widget _buildPageLoadingIndicatorState(LearnPracticeViewModel viewModel) =>
viewModel.busy(StateObjects.learnPracticeQuestion)
? const PageLoadingIndicator()
: Container();
}

View File

@ -76,8 +76,8 @@ class LearnPracticeCompletionScreen
height: 55,
borderRadius: 12,
text: 'Continue',
onTap: viewModel.pop,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(0),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,54 +1,79 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_practice.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart';
class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
final String title;
final String subtitle;
final String buttonLabel;
const LearnPracticeIntroScreen({super.key});
const LearnPracticeIntroScreen(
{super.key,
required this.title,
required this.subtitle,
required this.buttonLabel});
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop();
viewModel.pop();
viewModel.stopRecording();
}
Future<void> _showSheet(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
_buildScaffoldWrapper(context: context,viewModel: viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
body: _buildScaffold(context: context,viewModel: viewModel),
);
Widget _buildScaffold(LearnPracticeViewModel viewModel) =>
SafeArea(child: _buildColumnWrapper(viewModel));
Widget _buildScaffold( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
SafeArea(child: _buildColumnWrapper(context: context,viewModel: viewModel));
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding(
Widget _buildColumnWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
child: _buildColumn(context: context,viewModel: viewModel),
);
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
Widget _buildColumn( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
_buildAppBar(context: context,viewModel: viewModel),
verticalSpaceMedium,
_buildBodyColumnWrapper(viewModel),
],
);
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
Widget _buildAppBar( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => SmallAppBar(
showBackButton: true,
onTap: viewModel.goBack,
title: 'Practice Speaking',
onTap: () async => await _showSheet(context: context,viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Expanded(
@ -96,7 +121,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
);
Widget _buildPartnerName() => Text.rich(
TextSpan(text: 'Daniel', style: style14DG600, children: [
TextSpan(text: 'Dawit', style: style14DG600, children: [
TextSpan(
text: ' - Your Speaking Partner',
style: style14MG400,
@ -105,13 +130,14 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
);
Widget _buildTitle() => Text(
title,
'Let\'s practice what you just learnt!',
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
subtitle,
'Ill ask you a few questions, and you can respond naturally.',
maxLines: 1,
style: style14DG400,
textAlign: TextAlign.center,
);
@ -126,9 +152,9 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: buttonLabel,
text: 'Practice',
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(2),
onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_carousel_widget/flutter_carousel_widget.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_question.dart';
import 'package:yimaru_app/models/practice.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_card.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/small_app_bar.dart';
import '../learn_practice_viewmodel.dart';
import 'interact_learn_practice_screen.dart';
class LearnPracticeQuestionsScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeQuestionsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildQuestionSetView(viewModel);
Widget _buildQuestionSetView(LearnPracticeViewModel viewModel) =>
PageView.builder(
itemCount: viewModel.questions.length,
controller: viewModel.questionSetController,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) => _buildQuestionView(
index: index + 1,
viewModel: viewModel,
question: viewModel.questions[index]),
);
Widget _buildQuestionView(
{required int index,
required LearnQuestion question,
required LearnPracticeViewModel viewModel}) =>
PageView(
controller: viewModel.questionController,
physics: const NeverScrollableScrollPhysics(),
children: _buildScreens(index:index,question: question),
);
List<Widget> _buildScreens({
required int index,
required LearnQuestion question,
}) =>
[
_buildStartLearnPracticeScreen(index:index,question: question),
_buildInteractLearnPracticeScreen(index:index,question: question)
];
Widget _buildStartLearnPracticeScreen({
required int index,
required LearnQuestion question,}
) => StartLearnPracticeScreen(
index: index,
question: question,
);
Widget _buildInteractLearnPracticeScreen({
required int index,
required LearnQuestion question,}) =>
InteractLearnPracticeScreen(index: index,question: question,);
}

View File

@ -6,45 +6,84 @@ import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultScreen({super.key});
void _navigate(LearnPracticeViewModel viewModel){
viewModel.questionSetController.jumpToPage(0);
viewModel.goTo(0);
}
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop();
viewModel.pop();
viewModel.stopRecording();
}
Future<void> _showSheet(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
_buildScaffoldWrapper(context: context,viewModel: viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper({required BuildContext context,
required LearnPracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
body: _buildScaffold(context: context,viewModel: viewModel),
);
Widget _buildScaffold(LearnPracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildScaffold({required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
SafeArea(child: _buildBodyColumnWrapper(context: context,viewModel: viewModel));
Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper({required BuildContext context,
required LearnPracticeViewModel viewModel}) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
child: _buildBodyColumn(context: context,viewModel: viewModel),
);
Widget _buildBodyColumn(viewModel) => Column(
children: _buildBodyColumnChildren(viewModel),
Widget _buildBodyColumn({required BuildContext context,
required LearnPracticeViewModel viewModel}) => Column(
children: _buildBodyColumnChildren(context: context,viewModel: viewModel),
);
List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [
List<Widget> _buildBodyColumnChildren({required BuildContext context,
required LearnPracticeViewModel viewModel}) => [
verticalSpaceMedium,
_buildAppBar(viewModel),
_buildAppBar(context: context,viewModel: viewModel),
verticalSpaceMedium,
_buildBodyWrapper(viewModel)
];
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
Widget _buildAppBar({required BuildContext context,
required LearnPracticeViewModel viewModel}) => SmallAppBar(
title: 'Result',
showBackButton: true,
onTap: viewModel.goBack,
onTap: () async => await _showSheet(context: context,viewModel: viewModel),
);
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildBodyWrapper(LearnPracticeViewModel viewMode) => Expanded(
@ -71,9 +110,9 @@ class LearnPracticeResultScreen
];
Widget _buildResultsSection(LearnPracticeViewModel viewModel) =>
LearnPracticeResultsWrapper(data: viewModel.selectedPractice);
const LearnPracticeResultsWrapper();
Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection();
Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection();
Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
@ -96,7 +135,7 @@ class LearnPracticeResultScreen
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(6),
onTap: () => viewModel.goTo(4),
backgroundColor: kcPrimaryColor,
);
@ -107,7 +146,8 @@ class LearnPracticeResultScreen
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: () => viewModel.goTo(1),
foregroundColor: kcPrimaryColor,
onTap: () => _navigate(viewModel),
);
}

View File

@ -1,100 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_card.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/small_app_bar.dart';
import '../learn_practice_viewmodel.dart';
class LearnPracticesScreen extends ViewModelWidget<LearnPracticeViewModel> {
final List<Map<String, dynamic>> practices;
const LearnPracticesScreen({Key? key, required this.practices})
: super(key: key);
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnPracticeViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnPracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildPracticeColumnWrapper(viewModel),
],
);
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop,
showBackButton: true,
);
Widget _buildPracticeColumnWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildPracticeColumnScrollView(viewModel));
Widget _buildPracticeColumnScrollView(LearnPracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildPracticeColumn(viewModel),
);
Widget _buildPracticeColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildPracticeColumnChildren(viewModel),
);
List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) =>
[
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
verticalSpaceMedium,
_buildListView(viewModel)
];
Widget _buildTitle() => Text(
'Learn Practices',
style: style18DG700,
);
Widget _buildSubtitle() => Text(
'Select a practice test your progress',
style: style14DG400,
);
Widget _buildListView(LearnPracticeViewModel viewModel) => GridView.builder(
shrinkWrap: true,
itemCount: practices.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) =>
_buildCard(index: index, practice: practices[index]),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 15,
crossAxisSpacing: 15,
childAspectRatio: 1.45,
),
);
Widget _buildCard(
{required int index, required Map<String, dynamic> practice}) =>
LearnPracticeCard(
index: index + 1,
practice: practice,
);
}

View File

@ -3,57 +3,92 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_column_button.dart';
import '../../../../models/learn_question.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/small_app_bar.dart';
class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
const StartLearnPracticeScreen({super.key});
final int index;
final LearnQuestion question;
const StartLearnPracticeScreen({super.key,required this.index,required this.question});
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
viewModel.pop();
viewModel.pop();
viewModel.stopRecording();
}
void _start(LearnPracticeViewModel viewModel) {
viewModel.playQuestionAudio();
viewModel.playVoicePrompt(question);
}
Future<void> _showSheet(
{required BuildContext context,
required LearnPracticeViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: kcTransparent,
builder: (cxt) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
_buildScaffoldWrapper(context: context,viewModel: viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
Widget _buildScaffoldWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
body: _buildScaffold(context: context,viewModel: viewModel),
);
Widget _buildScaffold(LearnPracticeViewModel viewModel) =>
SafeArea(child: _buildBodyColumnWrapper(viewModel));
Widget _buildScaffold( {required BuildContext context,
required LearnPracticeViewModel viewModel}) =>
SafeArea(child: _buildBodyColumnWrapper(context: context,viewModel: viewModel));
Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Padding(
Widget _buildBodyColumnWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBodyColumn(viewModel),
child: _buildBodyColumn(context: context,viewModel: viewModel),
);
Widget _buildBodyColumn(LearnPracticeViewModel viewModel) => Column(
Widget _buildBodyColumn( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
children: _buildBodyColumnChildren(context: context,viewModel: viewModel),
);
List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
List<Widget> _buildBodyColumnChildren( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => [
_buildAppBarWrapper(context: context,viewModel: viewModel),
_buildStartButtonWrapper(viewModel),
_buildLowerButtonsSectionWrapper(viewModel)
];
Widget _buildAppBarWrapper(LearnPracticeViewModel viewModel) => Column(
Widget _buildAppBarWrapper( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
_buildAppBar(context: context,viewModel: viewModel),
verticalSpaceMedium,
],
);
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
showBackButton: true,
onTap: viewModel.goBack,
title: 'Practice Speaking',
Widget _buildAppBar( {required BuildContext context,
required LearnPracticeViewModel viewModel}) => SmallAppBar(
showBackButton: true,
onTap: () async => await _showSheet(context: context,viewModel: viewModel),
title: 'Practice Speaking ($index/${viewModel.questions.length})');
Widget _buildSheet(LearnPracticeViewModel viewModel) =>
CancelLearnPracticeSheet(
onClose: viewModel.pop,
onContinue: viewModel.pop,
user: viewModel.user?.firstName ?? '',
onCancel: () async => await _cancel(viewModel),
);
Widget _buildStartButtonWrapper(LearnPracticeViewModel viewModel) => Expanded(

View File

@ -0,0 +1,95 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_program.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/profile_app_bar.dart';
import '../../widgets/learn_program_tile.dart';
import 'learn_program_viewmodel.dart';
class LearnProgramView extends StackedView<LearnProgramViewModel> {
const LearnProgramView({Key? key}) : super(key: key);
@override
void onViewModelReady(LearnProgramViewModel viewModel) async {
await viewModel.getLearnPrograms();
super.onViewModelReady(viewModel);
}
@override
LearnProgramViewModel viewModelBuilder(BuildContext context) =>
LearnProgramViewModel();
@override
Widget builder(
BuildContext context,
LearnProgramViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnProgramViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnProgramViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnProgramViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnProgramViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildProgramsColumnWrapper(viewModel)
],
);
Widget _buildAppBar(LearnProgramViewModel viewModel) => ProfileAppBar(
name: viewModel.user?.firstName,
profileImage: viewModel.user?.profilePicture,
);
Widget _buildProgramsColumnWrapper(LearnProgramViewModel viewModel) =>
Expanded(child: _buildProgramsColumnScrollView(viewModel));
Widget _buildProgramsColumnScrollView(LearnProgramViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(LearnProgramViewModel viewModel) =>
viewModel.busy(StateObjects.learnPrograms)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnProgramViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.learnPrograms.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildTile(
program: viewModel.learnPrograms[index],
onTap: () async => await viewModel
.navigateToLearnCourse(viewModel.learnPrograms[index].id ?? 0),
),
);
Widget _buildTile({
required LearnProgram program,
required GestureTapCallback onTap,
}) =>
LearnProgramTile(onTap: onTap, program: program);
}

View File

@ -1,16 +1,16 @@
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_program.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../models/subcategory.dart';
import '../../../models/user.dart';
import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class LearnSubcategoryViewModel extends ReactiveViewModel {
class LearnProgramViewModel extends ReactiveViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
@ -29,26 +29,28 @@ class LearnSubcategoryViewModel extends ReactiveViewModel {
User? get user => _user;
// Learn subcategories
List<Subcategory> _subcategories = [];
// Learn programs
List<LearnProgram> _learnPrograms = [];
List<Subcategory> get subcategories => _subcategories;
List<LearnProgram> get learnPrograms => _learnPrograms;
// Navigation
Future<void> navigateToLearn(int id) async =>
_navigationService.navigateToLearnView(id: id);
Future<void> navigateToLearnCourse(int id) async =>
_navigationService.navigateToLearnCourseView(id: id);
// Remote api call
// Learn subcategories
Future<void> getLearnSubcategories() async =>
await runBusyFuture(_getLearnSubcategories(),
busyObject: StateObjects.learnSubcategories);
// Learn programs
Future<void> getLearnPrograms() async =>
await runBusyFuture(_getLearnPrograms(),
busyObject: StateObjects.learnPrograms);
Future<void> _getLearnSubcategories() async {
if (_subcategories.isEmpty) {
Future<void> _getLearnPrograms() async {
if (_learnPrograms.isEmpty) {
if (await _statusChecker.checkConnection()) {
_subcategories = await _apiService.getLearnSubcategories();
_learnPrograms = await _apiService.getLearnPrograms();
_learnPrograms
.sort((a, b) => (a.sortOrder ?? 0).compareTo(b.sortOrder ?? 0));
}
}
}

View File

@ -1,100 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/learn_subcategory_card.dart';
import '../../../models/subcategory.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/profile_app_bar.dart';
import 'learn_subcategory_viewmodel.dart';
class LearnSubcategoryView extends StackedView<LearnSubcategoryViewModel> {
const LearnSubcategoryView({Key? key}) : super(key: key);
@override
void onViewModelReady(LearnSubcategoryViewModel viewModel) async {
await viewModel.getLearnSubcategories();
super.onViewModelReady(viewModel);
}
@override
LearnSubcategoryViewModel viewModelBuilder(BuildContext context) =>
LearnSubcategoryViewModel();
@override
Widget builder(
BuildContext context,
LearnSubcategoryViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnSubcategoryViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnSubcategoryViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnSubcategoryViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnSubcategoryViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildSubcategoryColumnWrapper(viewModel)
],
);
Widget _buildAppBar(LearnSubcategoryViewModel viewModel) => ProfileAppBar(
name: viewModel.user?.firstName,
profileImage: viewModel.user?.profilePicture,
);
Widget _buildSubcategoryColumnWrapper(LearnSubcategoryViewModel viewModel) =>
Expanded(child: _buildSubcategoryColumnScrollView(viewModel));
Widget _buildSubcategoryColumnScrollView(
LearnSubcategoryViewModel viewModel) =>
SingleChildScrollView(
child: _buildListViewBuilder(viewModel),
);
Widget _buildListViewBuilder(LearnSubcategoryViewModel viewModel) =>
viewModel.busy(StateObjects.learnSubcategories)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnSubcategoryViewModel viewModel) =>
ListView.separated(
shrinkWrap: true,
itemCount: viewModel.subcategories.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildTile(
subcategory: viewModel.subcategories[index],
onTap: () async => await viewModel
.navigateToLearn(viewModel.subcategories[index].id ?? 0),
),
);
Widget _buildTile({
required Subcategory subcategory,
required GestureTapCallback onTap,
}) =>
LearnSubcategoryCard(
onTap: onTap,
subcategory: subcategory,
);
}

View File

@ -1,146 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/module.dart';
import 'package:yimaru_app/models/submodule.dart';
import 'package:yimaru_app/ui/widgets/course_module_banner.dart';
import 'package:yimaru_app/ui/widgets/learn_submodule_tile.dart';
import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/custom_elevated_button.dart';
import '../../widgets/overall_learn_progress.dart';
import '../../widgets/small_app_bar.dart';
import 'learn_submodule_viewmodel.dart';
class LearnSubmoduleView extends StackedView<LearnSubmoduleViewModel> {
final Module module;
@override
void onViewModelReady(LearnSubmoduleViewModel viewModel) async {
await viewModel.getSubmodules(module.id ?? 0);
super.onViewModelReady(viewModel);
}
const LearnSubmoduleView({Key? key, required this.module}) : super(key: key);
@override
LearnSubmoduleViewModel viewModelBuilder(BuildContext context) =>
LearnSubmoduleViewModel();
@override
Widget builder(
BuildContext context,
LearnSubmoduleViewModel viewModel,
Widget? child,
) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnSubmoduleViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel),
);
Widget _buildScaffold(LearnSubmoduleViewModel viewModel) =>
SafeArea(child: _buildBody(viewModel));
Widget _buildBody(LearnSubmoduleViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
);
Widget _buildColumn(LearnSubmoduleViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildModulesColumnWrapper(viewModel),
],
);
Widget _buildAppBar(LearnSubmoduleViewModel viewModel) => SmallAppBar(
title: 'Submodules',
onTap: viewModel.pop,
showBackButton: true,
);
Widget _buildModulesColumnWrapper(LearnSubmoduleViewModel viewModel) =>
Expanded(child: _buildLevelsColumnScrollView(viewModel));
Widget _buildLevelsColumnScrollView(LearnSubmoduleViewModel viewModel) =>
SingleChildScrollView(
child: _buildLevelsColumn(viewModel),
);
Widget _buildLevelsColumn(LearnSubmoduleViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildLevelsColumnChildren(viewModel),
);
List<Widget> _buildLevelsColumnChildren(LearnSubmoduleViewModel viewModel) =>
[
verticalSpaceMedium,
_buildTitle(),
verticalSpaceMedium,
_buildCourseModuleBanner(),
verticalSpaceMedium,
_buildOverallProgress(),
verticalSpaceTiny,
_buildContinueButton(viewModel),
verticalSpaceMedium,
_buildListViewBuilder(viewModel)
];
Widget _buildTitle() => Text(
module.title ?? '',
style: style18P600,
);
Widget _buildCourseModuleBanner() => const CourseModuleBanner();
Widget _buildOverallProgress() => const OverallLearnProgress(
color: Colors.transparent,
);
Widget _buildContinueButton(LearnSubmoduleViewModel viewModel) =>
const CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
text: 'Continue Submodule',
backgroundColor: kcPrimaryColor);
Widget _buildListViewBuilder(LearnSubmoduleViewModel viewModel) =>
viewModel.busy(StateObjects.learnSubmodules)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnSubmoduleViewModel viewModel) => ListView.builder(
shrinkWrap: true,
itemCount: viewModel.submodules.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
submodule: viewModel.submodules[index],
onPracticeTap: () {},
onLessonTap: () async => await viewModel
.navigateToLearnLessons(viewModel.submodules[index]),
),
);
Widget _buildTile({
required Submodule submodule,
required GestureTapCallback onLessonTap,
required GestureTapCallback onPracticeTap,
}) =>
LearnSubmoduleTile(
submodule: submodule,
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -1,46 +0,0 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart';
import '../../../models/submodule.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart';
class LearnSubmoduleViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn submodule
List<Submodule> _submodules = [];
List<Submodule> get submodules => _submodules;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnLessons(Submodule submodule) async =>
await _navigationService.navigateToLearnLessonView(submodule: submodule);
// Remote api call
// Learn modules
Future<void> getSubmodules(int id) async =>
await runBusyFuture(_getSubmodules(id),
busyObject: StateObjects.learnSubmodules);
Future<void> _getSubmodules(int id) async {
if (_submodules.isEmpty) {
if (await _statusChecker.checkConnection()) {
_submodules = await _apiService.getSubmodules(id);
_submodules.sort(
(a, b) => (a.displayOrder ?? 0).compareTo(b.displayOrder ?? 0));
}
}
}
}

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -219,13 +220,13 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setOtpValidationMessage(String? validationMessage) =>
void setOtpValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OtpValueKey] = validationMessage;
setPasswordValidationMessage(String? validationMessage) =>
void setPasswordValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
setEmailValidationMessage(String? validationMessage) =>
void setEmailValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
setPhoneNumberValidationMessage(String? validationMessage) =>
void setPhoneNumberValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
/// Clears text input fields on the Form
@ -253,7 +254,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_LoginViewTextEditingControllers[key]!.text,
_LoginViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -253,15 +254,15 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setTopicValidationMessage(String? validationMessage) =>
void setTopicValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[TopicValueKey] = validationMessage;
setFullNameValidationMessage(String? validationMessage) =>
void setFullNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
setChallengeValidationMessage(String? validationMessage) =>
void setChallengeValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ChallengeValueKey] = validationMessage;
setOccupationValidationMessage(String? validationMessage) =>
void setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
setLanguageGoalValidationMessage(String? validationMessage) =>
void setLanguageGoalValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LanguageGoalValueKey] = validationMessage;
/// Clears text input fields on the Form
@ -291,7 +292,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_OnboardingViewTextEditingControllers[key]!.text,
_OnboardingViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -257,15 +258,15 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setEmailValidationMessage(String? validationMessage) =>
void setEmailValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
setPhoneNumberValidationMessage(String? validationMessage) =>
void setPhoneNumberValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
setLastNameValidationMessage(String? validationMessage) =>
void setLastNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[LastNameValueKey] = validationMessage;
setFirstNameValidationMessage(String? validationMessage) =>
void setFirstNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FirstNameValueKey] = validationMessage;
setOccupationValidationMessage(String? validationMessage) =>
void setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
/// Clears text input fields on the Form
@ -295,7 +296,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_ProfileDetailViewTextEditingControllers[key]!.text,
_ProfileDetailViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -1,4 +1,5 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// dart format width=80
// **************************************************************************
// StackedFormGenerator
@ -252,15 +253,15 @@ extension ValueProperties on FormStateHelper {
}
extension Methods on FormStateHelper {
setOtpValidationMessage(String? validationMessage) =>
void setOtpValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OtpValueKey] = validationMessage;
setEmailValidationMessage(String? validationMessage) =>
void setEmailValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
setPasswordValidationMessage(String? validationMessage) =>
void setPasswordValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
setPhoneNumberValidationMessage(String? validationMessage) =>
void setPhoneNumberValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[PhoneNumberValueKey] = validationMessage;
setConfirmPasswordValidationMessage(String? validationMessage) =>
void setConfirmPasswordValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[ConfirmPasswordValueKey] =
validationMessage;
@ -291,7 +292,7 @@ String? getValidationMessage(String key) {
if (validatorForKey == null) return null;
String? validationMessageForKey = validatorForKey(
_RegisterViewTextEditingControllers[key]!.text,
_RegisterViewTextEditingControllers[key]?.text,
);
return validationMessageForKey;

View File

@ -1,19 +1,27 @@
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/services/authentication_service.dart';
import 'package:yimaru_app/services/in_app_update_service.dart';
import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../services/status_checker_service.dart';
class StartupViewModel extends BaseViewModel {
// Dependency injection
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
final _inAppUpdateService = locator<InAppUpdateService>();
final _authenticationService = locator<AuthenticationService>();
// Place anything here that needs to happen before we get into the application
Future runStartupLogic() async {
final loggedIn = await _authenticationService.userLoggedIn();
final firstTimeInstall = await _authenticationService.isFirstTimeInstall();
await _inAppUpdate();
if (firstTimeInstall) {
await _navigationService.replaceWithWelcomeView();
} else {
@ -26,4 +34,10 @@ class StartupViewModel extends BaseViewModel {
}
}
}
Future<void> _inAppUpdate() async {
if (await _statusChecker.checkConnection()) {
await _inAppUpdateService.checkForUpdate();
}
}
}

View File

@ -7,25 +7,17 @@ import '../../../app/app.locator.dart';
import '../../../services/status_checker_service.dart';
class WelcomeViewModel extends BaseViewModel {
// Dependency Injection
final _navigationService = locator<NavigationService>();
final _statusChecker = locator<StatusCheckerService>();
final _authenticationService = locator<AuthenticationService>();
int _currentPage = 0;
int get currentPage => _currentPage;
// Navigation
Future<void> navigateToLogin() async =>
await _navigationService.navigateToLoginView();
void next() {
_currentPage++;
rebuildUi();
}
// Remote api call
// First time install
@ -34,9 +26,7 @@ class WelcomeViewModel extends BaseViewModel {
}
Future<void> _setFirstTimeInstall() async {
if (await _statusChecker.checkConnection()) {
await _authenticationService.setFirstTimeInstall(false);
await navigateToLogin();
}
await _authenticationService.setFirstTimeInstall(false);
await navigateToLogin();
}
}

View File

@ -8,37 +8,38 @@ import '../common/ui_helpers.dart';
import 'custom_bottom_sheet.dart';
import 'custom_elevated_button.dart';
class CancelLearnPracticeSheet extends ViewModelWidget<LearnPracticeViewModel> {
class CancelLearnPracticeSheet extends StatelessWidget {
final String user;
final GestureTapCallback? onClose;
final GestureTapCallback? onCancel;
final GestureTapCallback? onContinue;
const CancelLearnPracticeSheet(
{super.key, this.onClose, this.onCancel, this.onContinue});
{super.key, this.onClose, this.onCancel, this.onContinue,required this.user});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildSheetWrapper(viewModel);
Widget build(BuildContext context) =>
_buildSheetWrapper();
Widget _buildSheetWrapper(LearnPracticeViewModel viewModel) =>
Widget _buildSheetWrapper() =>
CustomBottomSheet(
height: 500, onTap: onClose, child: _buildColumnWrapper(viewModel));
height: 500, onTap: onClose, child: _buildColumnWrapper());
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding(
Widget _buildColumnWrapper() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel),
child: _buildColumn(),
);
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
Widget _buildColumn() => Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildSheetChildren(viewModel),
children: _buildSheetChildren(),
);
List<Widget> _buildSheetChildren(LearnPracticeViewModel viewModel) => [
List<Widget> _buildSheetChildren() => [
verticalSpaceLarge,
_buildImage(),
verticalSpaceMedium,
_buildMessage(viewModel),
_buildMessage(),
_buildSubtitle(),
verticalSpaceLarge,
_buildContinueButton(),
@ -49,10 +50,10 @@ class CancelLearnPracticeSheet extends ViewModelWidget<LearnPracticeViewModel> {
radius: 45,
);
Widget _buildMessage(LearnPracticeViewModel viewModel) => Text.rich(
Widget _buildMessage() => Text.rich(
TextSpan(text: 'Youre almost there,', style: style18DG700, children: [
TextSpan(
text: ' ${viewModel.user?.firstName ?? ''}!',
text: ' $user',
style: style18P600,
)
]),

View File

@ -0,0 +1,166 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/widgets/progress_status.dart';
import '../../models/learn_course.dart';
import '../common/app_colors.dart';
import '../common/ui_helpers.dart';
import '../views/learn_course/learn_course_viewmodel.dart';
import 'custom_elevated_button.dart';
class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
final LearnCourse course;
final GestureTapCallback? onViewTap;
final GestureTapCallback? onPracticeTap;
const LearnCourseTile({
super.key,
this.onViewTap,
this.onPracticeTap,
required this.course,
});
@override
Widget build(BuildContext context, LearnCourseViewModel viewModel) =>
_buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCard(LearnCourseViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kcPrimaryColor.withOpacity(0.1)),
),
child: _buildExpansionTile(viewModel),
);
Widget _buildExpansionTile(LearnCourseViewModel viewModel) => ExpansionTile(
textColor: kcDarkGrey,
subtitle: _buildContent(),
title: _buildTitleWrapper(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
enabled: (course.access?.isAccessible ?? false),
backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing,
tilePadding: const EdgeInsets.fromLTRB(15, 0, 15, 5),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
showTrailingIcon: (course.access?.isAccessible ?? false),
initiallyExpanded: (course.access?.isAccessible ?? false),
collapsedBackgroundColor: (course.access?.isAccessible ?? false)
? kcPrimaryColor.withOpacity(0.1)
: kcBackgroundColor,
children: _buildExpansionTileChildren(viewModel),
);
List<Widget> _buildExpansionTileChildren(LearnCourseViewModel viewModel) => [
_buildDivider(),
verticalSpaceSmall,
_buildActionButtonsWrapper(viewModel),
verticalSpaceSmall,
];
Widget _buildTitleWrapper() => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: _buildTitleState(),
);
Widget _buildTitleState() => (course.access?.isAccessible ?? false)
? _buildActiveTitleWrapper()
: _buildInactiveTitleWrapper();
Widget _buildInactiveTitleWrapper() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildInactiveTitleChildren(),
);
List<Widget> _buildInactiveTitleChildren() => [
_buildLockIcon(),
verticalSpaceSmall,
_buildTitle(),
];
Widget _buildLockIcon() => const Icon(
Icons.lock_outline_rounded,
color: kcLightGrey,
);
Widget _buildActiveTitleWrapper() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildActiveTitleChildren(),
);
List<Widget> _buildActiveTitleChildren() =>
[_buildTitle(), horizontalSpaceSmall, _buildProgressStatusState()];
Widget _buildTitle() => Text(
course.name ?? '',
style:
(course.access?.isAccessible ?? false) ? style16P600 : style16DG600,
);
Widget _buildProgressStatusState() =>
!(course.access?.isCompleted ?? false) &&
course.access?.progressPercent != 0
? _buildProgressStatus()
: Container();
Widget _buildProgressStatus() => const ProgressStatus(
color: kcPrimaryColor,
status: 'Current Level',
);
Widget _buildContent() => Text(
course.description ?? '',
style: style14DG400,
);
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
Widget _buildActionButtonsWrapper(LearnCourseViewModel viewModel) =>
Container(
height: 40,
margin: const EdgeInsets.symmetric(horizontal: 15),
child: _buildActionButtons(viewModel),
);
Widget _buildActionButtons(LearnCourseViewModel viewModel) => Row(
children: [
_buildViewButtonWrapper(viewModel),
horizontalSpaceSmall,
_buildPracticeButtonWrapper(viewModel)
],
);
Widget _buildViewButtonWrapper(LearnCourseViewModel viewModel) => Expanded(
child: _buildModuleButton(viewModel),
);
Widget _buildModuleButton(LearnCourseViewModel viewModel) =>
CustomElevatedButton(
height: 15,
borderRadius: 12,
onTap: onViewTap,
text: 'View Courses',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
);
Widget _buildPracticeButtonWrapper(LearnCourseViewModel viewModel) =>
Expanded(
child: _buildPracticeButton(viewModel),
);
Widget _buildPracticeButton(LearnCourseViewModel viewModel) =>
CustomElevatedButton(
height: 15,
borderRadius: 12,
onTap: onPracticeTap,
text: 'View Practices',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
);
}

View File

@ -1,22 +1,28 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/models/lesson.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_lesson.dart';
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/mini_thumbnail.dart';
import '../common/app_colors.dart';
import '../common/helper_functions.dart';
import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart';
import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends StatelessWidget {
final Lesson lesson;
class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
final LearnLesson lesson;
final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
const LearnLessonTile({super.key, this.onLessonTap, required this.lesson});
const LearnLessonTile(
{super.key, this.onLessonTap, this.onPracticeTap, required this.lesson});
@override
Widget build(BuildContext context) => _buildContainer();
Widget build(BuildContext context, LearnLessonViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container(
Widget _buildContainer(LearnLessonViewModel viewModel) => Container(
width: double.maxFinite,
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
@ -28,27 +34,27 @@ class LearnLessonTile extends StatelessWidget {
// : kcGreen.withOpacity(0.1),
),
),
child: _buildExpansionTile(),
child: _buildExpansionTile(viewModel),
);
Widget _buildExpansionTile() => ExpansionTile(
Widget _buildExpansionTile(LearnLessonViewModel viewModel) => ExpansionTile(
enabled: true,
title: _buildTitle(),
textColor: kcDarkGrey,
showTrailingIcon: true,
initiallyExpanded: true,
initiallyExpanded: false,
trailing: _buildPendingIcon(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
leading: _buildLeadingWrapper(),
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
backgroundColor: kcGreen.withOpacity(0.1),
backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 0),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
childrenPadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
tilePadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
// enabled: status != ProgressStatuses.pending,
// backgroundColor: ProgressStatuses.pending == status
// ? kcPrimaryColor.withOpacity(0.1)
@ -57,14 +63,18 @@ class LearnLessonTile extends StatelessWidget {
// ? kcPrimaryColor.withOpacity(0.1)
// : kcGreen.withOpacity(0.1),
// initiallyExpanded: status != ProgressStatuses.completed ? true : false,
children: _buildExpansionTileChildren(),
children: _buildExpansionTileChildren(viewModel),
);
Widget _buildLeadingWrapper() =>
MiniThumbnail(thumbnail: lesson.thumbnail ?? 'assets/images/image_1.png');
Widget _buildLeadingWrapper() => MiniThumbnail(
thumbnail:
getReadableUrl(lesson.thumbnail ?? 'assets/images/image_1.png') ??
'assets/images/image_1.png');
Widget _buildTitle() => Text(
lesson.title ?? '',
maxLines: 1,
softWrap: false,
style: style16DG600,
);
@ -82,20 +92,23 @@ class LearnLessonTile extends StatelessWidget {
color: kcPrimaryColor,
);
List<Widget> _buildExpansionTileChildren() => [_buildExpansionTileItem()];
List<Widget> _buildExpansionTileChildren(LearnLessonViewModel viewModel) =>
[_buildExpansionTileItem(viewModel)];
Widget _buildExpansionTileItem() => Column(
Widget _buildExpansionTileItem(LearnLessonViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildExpansionTileItemChildren(),
children: _buildExpansionTileItemChildren(viewModel),
);
List<Widget> _buildExpansionTileItemChildren() => [
List<Widget> _buildExpansionTileItemChildren(
LearnLessonViewModel viewModel) =>
[
_buildProgress(),
horizontalSpaceSmall,
// _buildProgressText(),
// verticalSpaceSmall,
_buildActionButtonWrapper()
_buildProgressText(),
verticalSpaceSmall,
_buildActionButtonWrapper(viewModel)
];
Widget _buildProgress() => const CustomLinearProgressIndicator(
@ -104,17 +117,53 @@ class LearnLessonTile extends StatelessWidget {
backgroundColor: kcVeryLightGrey,
);
// Widget _buildProgressText() => Text(
// ProgressStatuses.completed == status ? 'Completed' : 'In Progress',
// style: style14P400,
// );
Widget _buildActionButtonWrapper() => SizedBox(
height: 50,
child: _buildLessonButton(),
Widget _buildProgressText() => Text(
'In Progress',
style: style14P600,
);
Widget _buildLessonButton() => CustomElevatedButton(
Widget _buildActionButtonWrapper(LearnLessonViewModel viewModel) => SizedBox(
height: 40,
child: _buildActionButtons(viewModel),
);
Widget _buildActionButtons(LearnLessonViewModel viewModel) => Row(
mainAxisAlignment: MainAxisAlignment.end,
children: __buildActionButtonChildren(viewModel),
);
List<Widget> __buildActionButtonChildren(LearnLessonViewModel viewModel) => [
_buildPracticeButtonWrapper(viewModel),
horizontalSpaceSmall,
_buildLessonButtonWrapper(viewModel)
];
Widget _buildPracticeButtonWrapper(LearnLessonViewModel viewModel) =>
SizedBox(
width: 125,
child: _buildPracticeButton(viewModel),
);
Widget _buildPracticeButton(LearnLessonViewModel viewModel) =>
CustomElevatedButton(
height: 15,
text: 'Practice',
borderRadius: 12,
onTap: onPracticeTap,
trailingIcon: Icons.mic,
width: double.maxFinite,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
);
Widget _buildLessonButtonWrapper(LearnLessonViewModel viewModel) => SizedBox(
width: 125,
child: _buildLessonButton(viewModel),
);
Widget _buildLessonButton(LearnLessonViewModel viewModel) =>
CustomElevatedButton(
height: 15,
text: 'Start',
borderRadius: 12,

View File

@ -1,98 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_level/learn_level_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/progress_status.dart';
import '../../models/level.dart';
import '../common/app_colors.dart';
import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart';
class LearnLevelTile extends ViewModelWidget<LearnLevelViewModel> {
final Level level;
final GestureTapCallback? onTap;
const LearnLevelTile({
super.key,
this.onTap,
required this.level,
});
@override
Widget build(BuildContext context, LearnLevelViewModel viewModel) =>
_buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCard(LearnLevelViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color: kcPrimaryColor.withOpacity(0.2),
// color:
// current ? kcPrimaryColor.withOpacity(0.2) : kcVeryLightGrey,
),
),
child: _buildExpansionTile(viewModel),
);
Widget _buildExpansionTile(LearnLevelViewModel viewModel) => ExpansionTile(
enabled: true,
textColor: kcDarkGrey,
showTrailingIcon: true,
title: _buildTitleRow(),
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,
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
childrenPadding: const EdgeInsets.only(left: 15, right: 15, bottom: 15),
//enabled: current,
// showTrailingIcon: current,
//initiallyExpanded: current,
// collapsedBackgroundColor:
// current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
// backgroundColor:
// current ? kcPrimaryColor.withOpacity(0.1) : kcBackgroundColor,
children: _buildExpansionTileChildren(viewModel),
);
List<Widget> _buildExpansionTileChildren(LearnLevelViewModel viewModel) =>
[_buildViewButton(viewModel)];
Widget _buildTitleRow() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildTitleChildren(),
);
List<Widget> _buildTitleChildren() => [
_buildTitle(),
// if (current) horizontalSpaceSmall,
// if (current) _buildProgressStatus()
];
Widget _buildTitle() => Text(
level.title ?? '',
style: style16P600,
);
Widget _buildProgressStatus() => const ProgressStatus(
color: kcPrimaryColor,
status: 'Current Level',
);
Widget _buildViewButton(LearnLevelViewModel viewModel) =>
CustomElevatedButton(
height: 15,
onTap: onTap,
borderRadius: 12,
text: 'View Level',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,19 +1,21 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/learn_module.dart';
import 'package:yimaru_app/ui/views/learn_module/learn_module_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
import '../../models/module.dart';
import '../common/app_colors.dart';
import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart';
class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
final Module module;
final LearnModule module;
final GestureTapCallback? onModuleTap;
final GestureTapCallback? onPracticeTap;
const LearnModuleTile({super.key, this.onModuleTap, required this.module});
const LearnModuleTile(
{super.key, this.onModuleTap, this.onPracticeTap, required this.module});
Future<void> _showSheet(
{required BuildContext context,
@ -35,7 +37,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kcVeryLightGrey),
border: Border.all(color: kcPrimaryColor.withOpacity(0.1)),
),
child: _buildTileStack(context: context, viewModel: viewModel),
);
@ -55,11 +57,11 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
required LearnModuleViewModel viewModel}) =>
ExpansionTile(
enabled: true,
title: _buildTitle(),
textColor: kcDarkGrey,
showTrailingIcon: true,
initiallyExpanded: true,
subtitle: _buildContent(),
title: _buildTitleWrapper(),
leading: _buildIconWrapper(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
@ -70,8 +72,8 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
childrenPadding: const EdgeInsets.fromLTRB(70, 15, 15, 15),
// enabled: status != ProgressStatuses.pending,
childrenPadding: const EdgeInsets.fromLTRB(70, 0, 15, 15),
// enabled:(module.access?.isAccessible ?? false) ,
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
children:
@ -88,8 +90,13 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
color: kcPrimaryColor,
);
Widget _buildTitleWrapper() => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: _buildTitle(),
);
Widget _buildTitle() => Text(
module.title ?? '',
module.name ?? '',
maxLines: 1,
softWrap: false,
style: style16P600,
@ -123,8 +130,8 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
[
// _buildProgressRow(),
// verticalSpaceSmall,
_buildProgressRow(),
verticalSpaceSmall,
_buildActionButtonWrapper(context: context, viewModel: viewModel)
];
@ -141,12 +148,12 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
);
Widget _buildProgressStatus() => const CustomLinearProgressIndicator(
progress: 0.75,
progress: 0,
activeColor: kcPrimaryColor,
backgroundColor: kcVeryLightGrey);
backgroundColor: kcPrimaryColorLight);
Widget _buildProgress() => const Text(
'2/3',
'0/0',
style: TextStyle(color: kcDarkGrey),
);
@ -155,17 +162,14 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
required LearnModuleViewModel viewModel}) =>
SizedBox(
height: 40,
child: _buildModuleButton(viewModel),
child: _buildActionButtons(viewModel),
);
Widget _buildActionButtons(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Row(
Widget _buildActionButtons(LearnModuleViewModel viewModel) => Row(
children: [
_buildModuleButtonWrapper(viewModel),
horizontalSpaceSmall,
_buildPracticeButtonWrapper(context: context, viewModel: viewModel)
_buildPracticeButtonWrapper(viewModel)
],
);
@ -183,23 +187,21 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
backgroundColor: kcPrimaryColor,
);
Widget _buildPracticeButtonWrapper(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
Widget _buildPracticeButtonWrapper(LearnModuleViewModel viewModel) =>
Expanded(
child: Container(),
child: _buildPracticeButton(viewModel),
);
Widget _buildPracticeButton(
{required BuildContext context,
required LearnModuleViewModel viewModel}) =>
const CustomElevatedButton(
Widget _buildPracticeButton(LearnModuleViewModel viewModel) =>
CustomElevatedButton(
height: 15,
borderRadius: 12,
text: 'View Practices',
onTap: onPracticeTap,
text: 'Take Practices',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
// onTap: () async => await viewModel.navigateToLearnPractice(practices),
);

View File

@ -8,11 +8,15 @@ import '../common/ui_helpers.dart';
import '../views/learn_practice/learn_practice_viewmodel.dart';
class LearnPracticeAnswerCard extends ViewModelWidget<LearnPracticeViewModel> {
final Voice voice;
final String title;
final StateObjects object;
final Map<String, dynamic> answer;
const LearnPracticeAnswerCard(
{super.key, required this.title, required this.object});
{super.key,
required this.voice,
required this.title,
required this.answer});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
@ -38,25 +42,20 @@ class LearnPracticeAnswerCard extends ViewModelWidget<LearnPracticeViewModel> {
shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
),
onPressed: () async => viewModel.player.state == PlayerState.playing
? viewModel.busyObject == StateObjects.learnPracticeSample
? await viewModel.pauseSampleAudio()
: await viewModel.pauseRecordedAudio()
: object == StateObjects.learnPracticeSample
? await viewModel.playSampleAudio()
: await viewModel.playRecordedAudio(),
onPressed: () async => viewModel.busyObject == answer['busy_object'] &&
viewModel.player.state == PlayerState.playing
? await viewModel.pauseAudio()
: await viewModel.playResult(answer: answer, voice: voice),
child: _buildButtonState(viewModel),
);
Widget _buildButtonState(LearnPracticeViewModel viewModel) =>
viewModel.busy(object)
? viewModel.busyObject == object
? _buildPauseIcon()
: _buildPlayIcon()
: viewModel.busyObject == object &&
viewModel.player.state == PlayerState.playing
? _buildPauseIcon()
: _buildPlayIcon();
(viewModel.busy(answer['busy_object']) && viewModel.playing == voice) ||
(viewModel.busyObject == answer['busy_object'] &&
viewModel.playing == voice &&
viewModel.player.state == PlayerState.playing)
? _buildPauseIcon()
: _buildPlayIcon();
Widget _buildPlayIcon() => const Icon(
Icons.play_arrow_rounded,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/practice.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../common/app_colors.dart';
@ -8,8 +9,8 @@ import 'custom_elevated_button.dart';
class LearnPracticeCard extends ViewModelWidget<LearnPracticeViewModel> {
final int index;
final Practice practice;
final GestureTapCallback? onTap;
final Map<String, dynamic> practice;
const LearnPracticeCard(
{super.key, this.onTap, required this.index, required this.practice});
@ -57,6 +58,6 @@ class LearnPracticeCard extends ViewModelWidget<LearnPracticeViewModel> {
text: 'Practice',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () => viewModel.setPractice(practice),
// onTap: () => viewModel.setPractice(practice),
);
}

View File

@ -6,30 +6,31 @@ import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart
import 'package:yimaru_app/ui/widgets/learn_practice_answer_card.dart';
class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
final Map<String, dynamic> data;
const LearnPracticeResultCard({super.key, required this.data});
final Map<String, dynamic> answer;
const LearnPracticeResultCard({super.key, required this.answer});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildColumnWrapper();
_buildColumnWrapper(viewModel);
Widget _buildColumnWrapper() => SizedBox(
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => SizedBox(
height: 100,
width: double.maxFinite,
child: _buildColumn(),
child: _buildColumn(viewModel),
);
Widget _buildColumn() => Column(
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
children: _buildColumnChildren(viewModel),
);
List<Widget> _buildColumnChildren() =>
[_buildQuestion(), verticalSpaceSmall, _buildRow()];
List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) =>
[_buildQuestion(viewModel), verticalSpaceSmall, _buildRow()];
Widget _buildQuestion() => Text(
data['question_text'],
Widget _buildQuestion(LearnPracticeViewModel viewModel) => Text(
answer['sample_text_answer'],
style: style14DG400,
);
@ -46,16 +47,18 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildSampleResponseWrapper() =>
Expanded(child: _buildSampleResponse());
Widget _buildSampleResponse() => const LearnPracticeAnswerCard(
Widget _buildSampleResponse() => LearnPracticeAnswerCard(
answer: answer,
voice: Voice.sample,
title: 'Sample Answer',
object: StateObjects.learnPracticeSample,
);
Widget _buildActualResponseWrapper() =>
Expanded(child: _buildActualResponse());
Widget _buildActualResponse() => const LearnPracticeAnswerCard(
Widget _buildActualResponse() => LearnPracticeAnswerCard(
answer: answer,
title: 'Your Answer',
object: StateObjects.learnPracticeAnswer,
voice: Voice.recorded,
);
}

View File

@ -1,34 +1,39 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_result_card.dart';
import '../common/app_colors.dart';
class LearnPracticeResultsWrapper extends StatelessWidget {
final Map<String, dynamic> data;
const LearnPracticeResultsWrapper({super.key, required this.data});
class LearnPracticeResultsWrapper
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultsWrapper({super.key});
@override
Widget build(BuildContext context) => _buildContainer();
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildContainer(viewModel);
Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
Widget _buildContainer(LearnPracticeViewModel viewModel) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: kcPrimaryColor.withOpacity(0.1),
),
child: _buildColumn(),
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
child: _buildColumn(viewModel),
);
Widget _buildColumn() => Column(
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
children: _buildColumnChildren(viewModel),
);
List<Widget> _buildColumnChildren() =>
[_buildTitle(), verticalSpaceSmall, if (data.isNotEmpty) _buildResult()];
List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) => [
_buildTitle(),
verticalSpaceSmall,
_buildResults(viewModel)
];
Widget _buildTitle() => Text(
'Conversation Review',
@ -36,5 +41,15 @@ class LearnPracticeResultsWrapper extends StatelessWidget {
textAlign: TextAlign.center,
);
Widget _buildResult() => LearnPracticeResultCard(data: data);
Widget _buildResults(LearnPracticeViewModel viewModel) => ListView.separated(
shrinkWrap: true,
itemCount: viewModel.answers.length,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => verticalSpaceSmall,
itemBuilder: (context, index) => _buildResult( viewModel.answers[index]),
);
Widget _buildResult(Map<String,dynamic> answer) => LearnPracticeResultCard(answer: answer,);
}

View File

@ -1,30 +1,34 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
class LearnPracticeTipSection extends StatelessWidget {
import '../../models/learn_practice.dart';
class LearnPracticeTipSection extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeTipSection({super.key});
@override
Widget build(BuildContext context) => _buildContainer();
Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildContainer(viewModel);
Widget _buildContainer() => Container(
Widget _buildContainer(LearnPracticeViewModel viewModel) => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: kcBlue.withOpacity(0.1),
),
child: _buildColumn(),
child: _buildColumn(viewModel),
);
Widget _buildColumn() => Column(
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
children: _buildColumnChildren( viewModel),
);
List<Widget> _buildColumnChildren() =>
[_buildTitleWrapper(), verticalSpaceTiny, _buildContent()];
List<Widget> _buildColumnChildren(LearnPracticeViewModel viewModel) =>
[_buildTitleWrapper(), verticalSpaceTiny, _buildContent(viewModel)];
Widget _buildTitleWrapper() => Row(
children: [_buildLeading(), horizontalSpaceSmall, _buildTitle()],
@ -40,8 +44,8 @@ class LearnPracticeTipSection extends StatelessWidget {
style: style16B600,
);
Widget _buildContent() => Text(
"You can always do better!\nSpeak in full sentences instead of short phrases.\nMaintain a steady pace, not too fast, not too slow.\nUse varied vocabulary to make your answers richer.\nPause naturally instead of using fillers like “um” or “uh”.",
Widget _buildContent(LearnPracticeViewModel viewModel) => Text(
viewModel.practices.firstOrNull?.quickTips ?? '',
style: style14B400,
textAlign: TextAlign.start,
);

View File

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/progress_status.dart';
import '../../models/learn_program.dart';
import '../common/app_colors.dart';
import '../views/learn_program/learn_program_viewmodel.dart';
import 'custom_elevated_button.dart';
class LearnProgramTile extends ViewModelWidget<LearnProgramViewModel> {
final LearnProgram program;
final GestureTapCallback? onTap;
const LearnProgramTile({super.key, this.onTap, required this.program});
@override
Widget build(BuildContext context, LearnProgramViewModel viewModel) =>
_buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCard(LearnProgramViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kcPrimaryColor.withOpacity(0.1)),
),
child: _buildExpansionTile(viewModel),
);
Widget _buildExpansionTile(LearnProgramViewModel viewModel) => ExpansionTile(
textColor: kcDarkGrey,
subtitle: _buildContent(),
title: _buildTitleWrapper(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
enabled: (program.access?.isAccessible ?? false),
backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing,
tilePadding: const EdgeInsets.fromLTRB(15, 0, 15, 5),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
showTrailingIcon: (program.access?.isAccessible ?? false),
initiallyExpanded: (program.access?.isAccessible ?? false),
collapsedBackgroundColor: (program.access?.isAccessible ?? false)
? kcPrimaryColor.withOpacity(0.1)
: kcBackgroundColor,
children: _buildExpansionTileChildren(viewModel),
);
List<Widget> _buildExpansionTileChildren(LearnProgramViewModel viewModel) => [
_buildDivider(),
verticalSpaceSmall,
_buildActionButtonWrapper(viewModel),
verticalSpaceMedium,
];
Widget _buildTitleWrapper() => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: _buildTitleState(),
);
Widget _buildTitleState() => (program.access?.isAccessible ?? false)
? _buildActiveTitleWrapper()
: _buildInactiveTitleWrapper();
Widget _buildInactiveTitleWrapper() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildInactiveTitleChildren(),
);
List<Widget> _buildInactiveTitleChildren() => [
_buildLockIcon(),
verticalSpaceSmall,
_buildTitle(),
];
Widget _buildLockIcon() => const Icon(
Icons.lock_outline_rounded,
color: kcLightGrey,
);
Widget _buildActiveTitleWrapper() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildActiveTitleChildren(),
);
List<Widget> _buildActiveTitleChildren() =>
[_buildTitle(), horizontalSpaceSmall, _buildProgressStatus()];
Widget _buildTitle() => Text(
program.name ?? '',
style: (program.access?.isAccessible ?? false)
? style16P600
: style16DG600,
);
Widget _buildProgressStatus() => ProgressStatus(
color: kcPrimaryColor,
status: (program.access?.isCompleted ?? false)
? 'Completed'
: 'In Progress',
);
Widget _buildContent() => Text(
program.description ?? '',
style: style14DG400,
);
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
Widget _buildActionButtonWrapper(LearnProgramViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildActionButton(viewModel),
);
Widget _buildActionButton(LearnProgramViewModel viewModel) =>
CustomElevatedButton(
height: 15,
onTap: onTap,
borderRadius: 12,
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
text: program.access?.progressPercent == 0
? 'Start Learning'
: 'Continue Learning',
);
}

View File

@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/models/subcategory.dart';
import '../common/app_colors.dart';
import '../common/app_strings.dart';
import '../common/helper_functions.dart';
import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart';
class LearnSubcategoryCard extends StatelessWidget {
final Subcategory subcategory;
final GestureTapCallback? onTap;
const LearnSubcategoryCard(
{super.key, this.onTap, required this.subcategory});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
height: 200,
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: getColor(),
borderRadius: BorderRadius.circular(5),
),
child: _buildColumn(),
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [
_buildTitle(),
verticalSpaceTiny,
_buildSubtitle(),
verticalSpaceMedium,
__buildStartButtonWrapper(),
];
Widget _buildTitle() => Text(
subcategory.name ?? '',
style: style18DG700,
);
Widget _buildSubtitle() => Text(
subcategory.description ?? ksCategorySubtitle,
maxLines: 3,
style: style16DG400,
);
Widget __buildStartButtonWrapper() => SizedBox(
height: 40,
child: _buildStartButton(),
);
Widget _buildStartButton() => CustomElevatedButton(
height: 50,
width: 200,
onTap: onTap,
borderRadius: 12,
text: 'Select Course',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
);
}

View File

@ -1,237 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/submodule.dart';
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/ui_helpers.dart';
import '../views/learn_submodule/learn_submodule_viewmodel.dart';
import 'custom_elevated_button.dart';
class LearnSubmoduleTile extends ViewModelWidget<LearnSubmoduleViewModel> {
final Submodule submodule;
final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
const LearnSubmoduleTile(
{super.key,
this.onLessonTap,
this.onPracticeTap,
required this.submodule});
Future<void> _showSheet(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) async =>
await showModalBottomSheet(
context: context,
backgroundColor: kcTransparent,
builder: (_) => _buildSheet(viewModel),
);
@override
Widget build(BuildContext context, LearnSubmoduleViewModel viewModel) =>
_buildExpansionTileCard(context: context, viewModel: viewModel);
Widget _buildExpansionTileCard(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kcVeryLightGrey),
),
child: _buildTileStack(context: context, viewModel: viewModel),
);
Widget _buildTileStack(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
Stack(
children: [
_buildExpansionTile(context: context, viewModel: viewModel),
// _buildContainerShaderState()
],
);
Widget _buildExpansionTile(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
ExpansionTile(
enabled: true,
title: _buildTitle(),
textColor: kcDarkGrey,
showTrailingIcon: true,
initiallyExpanded: true,
subtitle: _buildContent(),
leading: _buildIconWrapper(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
backgroundColor: kcBackgroundColor,
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
collapsedBackgroundColor: kcBackgroundColor,
controlAffinity: ListTileControlAffinity.trailing,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
childrenPadding: const EdgeInsets.fromLTRB(70, 15, 15, 15),
// enabled: status != ProgressStatuses.pending,
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
//initiallyExpanded: status == ProgressStatuses.started ? true : false,
children:
_buildExpansionTileChildren(context: context, viewModel: viewModel),
);
Widget _buildIconWrapper() => CircleAvatar(
backgroundColor: kcPrimaryColor.withOpacity(0.1),
child: _buildIcon(),
);
Widget _buildIcon() => const Icon(
Icons.lightbulb_outline,
color: kcPrimaryColor,
);
Widget _buildTitle() => Text(
submodule.title ?? '',
maxLines: 1,
softWrap: false,
style: style16P600,
overflow: TextOverflow.ellipsis,
);
Widget _buildContent() => Text(
submodule.description ?? '',
maxLines: 1,
softWrap: false,
style: style14DG400,
overflow: TextOverflow.ellipsis,
);
List<Widget> _buildExpansionTileChildren(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
[_buildExpansionTileItem(context: context, viewModel: viewModel)];
Widget _buildExpansionTileItem(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildExpansionTileItemChildren(
context: context, viewModel: viewModel),
);
List<Widget> _buildExpansionTileItemChildren(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
[
// _buildProgressRow(),
// verticalSpaceSmall,
_buildActionButtonWrapper(context: context, viewModel: viewModel)
];
Widget _buildProgressRow() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildProgressChildren(),
);
List<Widget> _buildProgressChildren() =>
[_buildProgressStatusWrapper(), horizontalSpaceSmall, _buildProgress()];
Widget _buildProgressStatusWrapper() => Expanded(
child: _buildProgressStatus(),
);
Widget _buildProgressStatus() => const CustomLinearProgressIndicator(
progress: 0.75,
activeColor: kcPrimaryColor,
backgroundColor: kcVeryLightGrey);
Widget _buildProgress() => const Text(
'2/3',
style: TextStyle(color: kcDarkGrey),
);
Widget _buildActionButtonWrapper(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
SizedBox(
height: 40,
child: _buildActionButtons(context: context, viewModel: viewModel),
);
Widget _buildActionButtons(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
Row(
children: [
_buildLessonButtonWrapper(viewModel),
horizontalSpaceSmall,
_buildPracticeButtonWrapper(context: context, viewModel: viewModel)
],
);
Widget _buildLessonButtonWrapper(LearnSubmoduleViewModel viewModel) =>
Expanded(
child: _buildLessonButton(viewModel),
);
Widget _buildLessonButton(LearnSubmoduleViewModel viewModel) =>
CustomElevatedButton(
height: 15,
borderRadius: 12,
onTap: onLessonTap,
text: 'View Module',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
// onTap: () async => await viewModel.navigateToLearnLesson(
// title: title,
// topics: topics,
// subtitle: subtitle,
// practices: practices,
// description: description),
);
Widget _buildPracticeButtonWrapper(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
Expanded(
child: _buildPracticeButton(context: context, viewModel: viewModel),
);
Widget _buildPracticeButton(
{required BuildContext context,
required LearnSubmoduleViewModel viewModel}) =>
CustomElevatedButton(
height: 15,
borderRadius: 12,
onTap: onPracticeTap,
text: 'View Practices',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
// onTap: () async => await viewModel.navigateToLearnPractice(practices),
);
Widget _buildSheet(LearnSubmoduleViewModel viewModel) => FinishPracticeSheet(
onTap: viewModel.pop,
);
// Widget _buildContainerShaderState() => status == ProgressStatuses.pending
// ? _buildContainerShaderWrapper()
// : Container();
Widget _buildContainerShaderWrapper() => Positioned.fill(
child: _buildContainerShader(),
);
Widget _buildContainerShader() => Container(
decoration: BoxDecoration(
color: kcWhite.withOpacity(0.5),
borderRadius: BorderRadius.circular(5),
),
);
}

View File

@ -1,123 +0,0 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/course.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/views/learn/learn_viewmodel.dart';
import '../common/app_colors.dart';
import 'custom_elevated_button.dart';
class LearnTile extends ViewModelWidget<LearnViewModel> {
final Course course;
final GestureTapCallback? onTap;
const LearnTile({
super.key,
this.onTap,
required this.course,
});
@override
Widget build(BuildContext context, LearnViewModel viewModel) =>
_buildExpansionTileCard(viewModel);
Widget _buildExpansionTileCard(LearnViewModel viewModel) => Container(
margin: const EdgeInsets.only(bottom: 15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: kcPrimaryColor.withOpacity(0.2)
// color: status == ProgressStatuses.started
// ? kcPrimaryColor.withOpacity(0.2)
// : kcVeryLightGrey,
),
),
child: _buildExpansionTile(viewModel),
);
Widget _buildExpansionTile(LearnViewModel viewModel) => ExpansionTile(
enabled: true,
textColor: kcDarkGrey,
showTrailingIcon: true,
title: _buildTitleRow(),
initiallyExpanded: false,
subtitle: _buildContent(),
collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey,
shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft,
backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing,
childrenPadding: const EdgeInsets.only(bottom: 15),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.symmetric(horizontal: 15),
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
// enabled: status != ProgressStatuses.pending ? true : false,
// status != ProgressStatuses.pending
// ? kcPrimaryColor.withOpacity(0.1)
// : kcBackgroundColor,
// collapsedBackgroundColor: status != ProgressStatuses.pending
// ? kcPrimaryColor.withOpacity(0.1)
// : kcBackgroundColor,
// showTrailingIcon: status != ProgressStatuses.pending ? true : false,
// initiallyExpanded: status == ProgressStatuses.started ? true : false,
children: _buildExpansionTileChildren(viewModel),
);
List<Widget> _buildExpansionTileChildren(LearnViewModel viewModel) => [
_buildDivider(),
verticalSpaceTiny,
_buildActionButtonWrapper(viewModel)
];
Widget _buildTitleRow() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildTitleChildren(),
);
List<Widget> _buildTitleChildren() => [
_buildTitle(),
// if (status != ProgressStatuses.pending) horizontalSpaceSmall,
// if (status != ProgressStatuses.pending) _buildProgressStatus()
];
Widget _buildTitle() => Text(
course.title ?? '',
style: const TextStyle(
fontSize: 16,
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
),
);
// Widget _buildProgressStatus() => ProgressStatus(
// status: status.name.substring(0, 1).toUpperCase() +
// status.name.substring(1, status.name.length),
// color: kcPrimaryColor,
// );
Widget _buildContent() => Text(
course.description ?? '',
style: style14DG400,
);
Widget _buildDivider() => const Divider(color: kcVeryLightGrey);
Widget _buildActionButtonWrapper(LearnViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildActionButton(viewModel),
);
Widget _buildActionButton(LearnViewModel viewModel) => CustomElevatedButton(
height: 15,
onTap: onTap,
borderRadius: 12,
text: 'Start Course',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
// text: status == ProgressStatuses.completed
// ? 'Review Course'
// : status == ProgressStatuses.pending
// ? 'Start Learning'
// : 'Continue Learning',
);
}

View File

@ -4,16 +4,20 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
class OverallLearnProgress extends StatelessWidget {
final Color color;
const OverallLearnProgress({super.key, required this.color});
final Color backgroundColor;
final Color indicatorBackgroundColor;
const OverallLearnProgress(
{super.key,
required this.backgroundColor,
required this.indicatorBackgroundColor});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 20),
decoration: BoxDecoration(
color: color,
color: backgroundColor,
borderRadius: BorderRadius.circular(4),
),
child: _buildProgressSection(),
@ -51,10 +55,10 @@ class OverallLearnProgress extends StatelessWidget {
style: style14P400,
);
Widget _buildProgressIndicator() => const CustomLinearProgressIndicator(
Widget _buildProgressIndicator() => CustomLinearProgressIndicator(
progress: 0.0,
activeColor: kcPrimaryColor,
backgroundColor: kcVeryLightGrey,
backgroundColor: indicatorBackgroundColor,
);
Widget _buildSubtitle() => const Text(

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
class ProgressStatus extends StatelessWidget {
final Color color;
@ -20,10 +21,6 @@ class ProgressStatus extends StatelessWidget {
Widget _buildLabel() => Text(
status,
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w600,
),
style: style12RP600.copyWith(color: color),
);
}

View File

@ -106,7 +106,7 @@ class SelectableCoursePracticeQuestion
index + 1 < viewModel.coursePracticeQuestions.length
? index + 1
: index]
.questionId ??
.id ??
0)
: null,
);

View File

@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
url: "https://pub.dev"
source: hosted
version: "67.0.0"
version: "91.0.0"
_flutterfire_internals:
dependency: transitive
description:
@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
url: "https://pub.dev"
source: hosted
version: "6.4.1"
version: "8.4.1"
ansicolor:
dependency: transitive
description:
@ -149,18 +149,18 @@ packages:
dependency: transitive
description:
name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
sha256: aadd943f4f8cc946882c954c187e6115a84c98c81ad1d9c6cbf0895a8c85da9c
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "4.0.5"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.3.0"
build_daemon:
dependency: transitive
description:
@ -169,30 +169,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
sha256: "4425a87d87d0d1303540f867994303f5b141ad2f6ecac7ac2cf8851f41c0cef1"
url: "https://pub.dev"
source: hosted
version: "2.4.13"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev"
source: hosted
version: "7.3.2"
version: "2.14.0"
built_collection:
dependency: transitive
description:
@ -357,10 +341,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
url: "https://pub.dev"
source: hosted
version: "2.3.6"
version: "3.1.3"
dbus:
dependency: transitive
description:
@ -772,18 +756,10 @@ packages:
dependency: transitive
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "2.4.4"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
version: "3.1.0"
get:
dependency: transitive
description:
@ -1032,14 +1008,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation:
dependency: "direct main"
description:
@ -1052,10 +1020,10 @@ packages:
dependency: "direct main"
description:
name: json_serializable
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3
url: "https://pub.dev"
source: hosted
version: "6.8.0"
version: "6.11.2"
leak_tracker:
dependency: transitive
description:
@ -1100,10 +1068,10 @@ packages:
dependency: transitive
description:
name: logger
sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f"
sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "2.7.0"
logging:
dependency: transitive
description:
@ -1148,10 +1116,10 @@ packages:
dependency: "direct dev"
description:
name: mockito
sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917"
sha256: eff30d002f0c8bf073b6f929df4483b543133fcafce056870163587b03f1d422
url: "https://pub.dev"
source: hosted
version: "5.4.4"
version: "5.6.4"
native_toolchain_c:
dependency: transitive
description:
@ -1529,18 +1497,18 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
sha256: "732792cfd197d2161a65bb029606a46e0a18ff30ef9e141a7a82172b05ea8ecd"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "4.2.2"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
url: "https://pub.dev"
source: hosted
version: "1.3.5"
version: "1.3.8"
source_span:
dependency: transitive
description:
@ -1609,10 +1577,10 @@ packages:
dependency: "direct dev"
description:
name: stacked_generator
sha256: eaa6447e3fd4d4010b746629b5518364d7fa7f6453ffb6416ad449fd352d1181
sha256: "251344e41a090226aeaca5d174882a7a6e4ff834feed6c9520ebee78850164a6"
url: "https://pub.dev"
source: hosted
version: "1.6.1"
version: "1.6.2"
stacked_services:
dependency: "direct main"
description:
@ -1693,14 +1661,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.10.1"
timing:
dependency: transitive
description:
name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
toastification:
dependency: "direct main"
description:

View File

@ -1,7 +1,8 @@
name: yimaru_app
description: A new Flutter project.
version: 0.1.4+6
publish_to: 'none'
version: 0.1.3+5
description: A new Flutter project.
environment:
sdk: '>=3.0.3 <4.0.0'

View File

@ -16,6 +16,7 @@ import 'package:yimaru_app/services/smart_auth_service.dart';
import 'package:yimaru_app/services/course_service.dart';
import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_service.dart';
import 'package:yimaru_app/services/in_app_update_service.dart';
// @stacked-import
import 'test_helpers.mocks.dart';
@ -42,6 +43,7 @@ import 'test_helpers.mocks.dart';
MockSpec<CourseService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<AudioPlayerService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<VoiceRecorderService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<InAppUpdateService>(onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec
],
)
@ -63,6 +65,7 @@ void registerServices() {
getAndRegisterCourseService();
getAndRegisterAudioPlayerService();
getAndRegisterVoiceRecorderService();
getAndRegisterInAppUpdateService();
// @stacked-mock-register
}
@ -217,6 +220,13 @@ MockVoiceRecorderService getAndRegisterVoiceRecorderService() {
locator.registerSingleton<VoiceRecorderService>(service);
return service;
}
MockInAppUpdateService getAndRegisterInAppUpdateService() {
_removeRegistrationIfExists<InAppUpdateService>();
final service = MockInAppUpdateService();
locator.registerSingleton<InAppUpdateService>(service);
return service;
}
// @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() {

View File

@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.4 from annotations
// Mocks generated by Mockito 5.4.6 from annotations
// in yimaru_app/test/helpers/test_helpers.dart.
// Do not manually edit this file.
@ -8,42 +8,48 @@ import 'dart:ui' as _i10;
import 'package:audioplayers/audioplayers.dart' as _i4;
import 'package:dio/dio.dart' as _i2;
import 'package:firebase_messaging/firebase_messaging.dart' as _i34;
import 'package:firebase_messaging/firebase_messaging.dart' as _i39;
import 'package:flutter/material.dart' as _i8;
import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i7;
import 'package:permission_handler/permission_handler.dart' as _i29;
import 'package:permission_handler/permission_handler.dart' as _i34;
import 'package:stacked_services/stacked_services.dart' as _i6;
import 'package:waveform_recorder/waveform_recorder.dart' as _i5;
import 'package:yimaru_app/models/category.dart' as _i15;
import 'package:yimaru_app/models/course.dart' as _i21;
import 'package:yimaru_app/models/course_detail.dart' as _i37;
import 'package:yimaru_app/models/course_lesson.dart' as _i18;
import 'package:yimaru_app/models/course_progress.dart' as _i17;
import 'package:yimaru_app/models/lesson.dart' as _i25;
import 'package:yimaru_app/models/level.dart' as _i22;
import 'package:yimaru_app/models/module.dart' as _i23;
import 'package:yimaru_app/models/practice.dart' as _i19;
import 'package:yimaru_app/models/practice_question.dart' as _i20;
import 'package:yimaru_app/models/category.dart' as _i21;
import 'package:yimaru_app/models/course.dart' as _i26;
import 'package:yimaru_app/models/course_detail.dart' as _i42;
import 'package:yimaru_app/models/course_lesson.dart' as _i24;
import 'package:yimaru_app/models/course_progress.dart' as _i23;
import 'package:yimaru_app/models/learn_course.dart' as _i16;
import 'package:yimaru_app/models/learn_lesson.dart' as _i19;
import 'package:yimaru_app/models/learn_module.dart' as _i18;
import 'package:yimaru_app/models/learn_practice.dart' as _i17;
import 'package:yimaru_app/models/learn_program.dart' as _i15;
import 'package:yimaru_app/models/learn_question.dart' as _i20;
import 'package:yimaru_app/models/lesson.dart' as _i30;
import 'package:yimaru_app/models/level.dart' as _i27;
import 'package:yimaru_app/models/module.dart' as _i28;
import 'package:yimaru_app/models/practice.dart' as _i25;
import 'package:yimaru_app/models/question.dart' as _i14;
import 'package:yimaru_app/models/subcategory.dart' as _i16;
import 'package:yimaru_app/models/submodule.dart' as _i24;
import 'package:yimaru_app/models/subcategory.dart' as _i22;
import 'package:yimaru_app/models/submodule.dart' as _i29;
import 'package:yimaru_app/models/user.dart' as _i12;
import 'package:yimaru_app/services/api_service.dart' as _i13;
import 'package:yimaru_app/services/audio_player_service.dart' as _i38;
import 'package:yimaru_app/services/audio_player_service.dart' as _i43;
import 'package:yimaru_app/services/authentication_service.dart' as _i11;
import 'package:yimaru_app/services/course_service.dart' as _i36;
import 'package:yimaru_app/services/dio_service.dart' as _i26;
import 'package:yimaru_app/services/google_auth_service.dart' as _i31;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i32;
import 'package:yimaru_app/services/image_picker_service.dart' as _i30;
import 'package:yimaru_app/services/notification_service.dart' as _i33;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i28;
import 'package:yimaru_app/services/course_service.dart' as _i41;
import 'package:yimaru_app/services/dio_service.dart' as _i31;
import 'package:yimaru_app/services/google_auth_service.dart' as _i36;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i37;
import 'package:yimaru_app/services/image_picker_service.dart' as _i35;
import 'package:yimaru_app/services/in_app_update_service.dart' as _i46;
import 'package:yimaru_app/services/notification_service.dart' as _i38;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i33;
import 'package:yimaru_app/services/secure_storage_service.dart' as _i3;
import 'package:yimaru_app/services/smart_auth_service.dart' as _i35;
import 'package:yimaru_app/services/status_checker_service.dart' as _i27;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i39;
import 'package:yimaru_app/ui/common/enmus.dart' as _i40;
import 'package:yimaru_app/services/smart_auth_service.dart' as _i40;
import 'package:yimaru_app/services/status_checker_service.dart' as _i32;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i44;
import 'package:yimaru_app/ui/common/enmus.dart' as _i45;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -53,10 +59,12 @@ import 'package:yimaru_app/ui/common/enmus.dart' as _i40;
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: must_be_immutable
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
// ignore_for_file: invalid_use_of_internal_member
class _FakeDio_0 extends _i1.SmartFake implements _i2.Dio {
_FakeDio_0(
@ -171,7 +179,7 @@ class MockNavigationService extends _i1.Mock implements _i6.NavigationService {
_i9.Future<T?>? navigateWithTransition<T>(
_i8.Widget? page, {
bool? opaque,
String? transition = r'',
String? transition = '',
Duration? duration,
bool? popGesture,
int? id,
@ -207,7 +215,7 @@ class MockNavigationService extends _i1.Mock implements _i6.NavigationService {
_i9.Future<T?>? replaceWithTransition<T>(
_i8.Widget? page, {
bool? opaque,
String? transition = r'',
String? transition = '',
Duration? duration,
bool? popGesture,
int? id,
@ -480,7 +488,7 @@ class MockBottomSheetService extends _i1.Mock
_i9.Future<_i6.SheetResponse<dynamic>?> showBottomSheet({
required String? title,
String? description,
String? confirmButtonTitle = r'Ok',
String? confirmButtonTitle = 'Ok',
String? cancelButtonTitle,
bool? enableDrag = true,
bool? barrierDismissible = true,
@ -533,7 +541,7 @@ class MockBottomSheetService extends _i1.Mock
double? elevation = 1.0,
bool? barrierDismissible = true,
bool? isScrollControlled = false,
String? barrierLabel = r'',
String? barrierLabel = '',
dynamic customData,
R? data,
bool? enableDrag = true,
@ -630,7 +638,7 @@ class MockDialogService extends _i1.Mock implements _i6.DialogService {
String? description,
String? cancelTitle,
_i10.Color? cancelTitleColor,
String? buttonTitle = r'Ok',
String? buttonTitle = 'Ok',
_i10.Color? buttonTitleColor,
bool? barrierDismissible = false,
_i8.RouteSettings? routeSettings,
@ -675,7 +683,7 @@ class MockDialogService extends _i1.Mock implements _i6.DialogService {
bool? takesInput = false,
_i10.Color? barrierColor = const _i10.Color(2315255808),
bool? barrierDismissible = false,
String? barrierLabel = r'',
String? barrierLabel = '',
bool? useSafeArea = true,
_i8.RouteSettings? routeSettings,
_i8.GlobalKey<_i8.NavigatorState>? navigatorKey,
@ -719,9 +727,9 @@ class MockDialogService extends _i1.Mock implements _i6.DialogService {
_i9.Future<_i6.DialogResponse<dynamic>?> showConfirmationDialog({
String? title,
String? description,
String? cancelTitle = r'Cancel',
String? cancelTitle = 'Cancel',
_i10.Color? cancelTitleColor,
String? confirmationTitle = r'Ok',
String? confirmationTitle = 'Ok',
_i10.Color? confirmationTitleColor,
bool? barrierDismissible = false,
_i8.RouteSettings? routeSettings,
@ -1128,54 +1136,157 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
) as _i9.Future<List<_i14.Question>>);
@override
_i9.Future<List<_i15.Category>> getCategories() => (super.noSuchMethod(
_i9.Future<List<_i15.LearnProgram>> getLearnPrograms() => (super.noSuchMethod(
Invocation.method(
#getLearnPrograms,
[],
),
returnValue:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]),
returnValueForMissingStub:
_i9.Future<List<_i15.LearnProgram>>.value(<_i15.LearnProgram>[]),
) as _i9.Future<List<_i15.LearnProgram>>);
@override
_i9.Future<List<_i16.LearnCourse>> getLearnCourse(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnCourse,
[id],
),
returnValue:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]),
returnValueForMissingStub:
_i9.Future<List<_i16.LearnCourse>>.value(<_i16.LearnCourse>[]),
) as _i9.Future<List<_i16.LearnCourse>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnCoursePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnCoursePractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
@override
_i9.Future<List<_i18.LearnModule>> getLearnModules(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnModules,
[id],
),
returnValue:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]),
returnValueForMissingStub:
_i9.Future<List<_i18.LearnModule>>.value(<_i18.LearnModule>[]),
) as _i9.Future<List<_i18.LearnModule>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnModulePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnModulePractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
@override
_i9.Future<List<_i19.LearnLesson>> getLearnLessons(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnLessons,
[id],
),
returnValue:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i19.LearnLesson>>.value(<_i19.LearnLesson>[]),
) as _i9.Future<List<_i19.LearnLesson>>);
@override
_i9.Future<List<_i17.LearnPractice>> getLearnLessonPractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnLessonPractices,
[id],
),
returnValue:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
returnValueForMissingStub:
_i9.Future<List<_i17.LearnPractice>>.value(<_i17.LearnPractice>[]),
) as _i9.Future<List<_i17.LearnPractice>>);
@override
_i9.Future<List<_i20.LearnQuestion>> getLearnQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getLearnQuestions,
[id],
),
returnValue:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]),
returnValueForMissingStub:
_i9.Future<List<_i20.LearnQuestion>>.value(<_i20.LearnQuestion>[]),
) as _i9.Future<List<_i20.LearnQuestion>>);
@override
_i9.Future<List<_i21.Category>> getCategories() => (super.noSuchMethod(
Invocation.method(
#getCategories,
[],
),
returnValue: _i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
returnValue: _i9.Future<List<_i21.Category>>.value(<_i21.Category>[]),
returnValueForMissingStub:
_i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
) as _i9.Future<List<_i15.Category>>);
_i9.Future<List<_i21.Category>>.value(<_i21.Category>[]),
) as _i9.Future<List<_i21.Category>>);
@override
_i9.Future<List<_i16.Subcategory>> getSubcategories(int? id) =>
_i9.Future<List<_i22.Subcategory>> getSubcategories(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubcategories,
[id],
),
returnValue:
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
returnValueForMissingStub:
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
) as _i9.Future<List<_i16.Subcategory>>);
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>);
@override
_i9.Future<List<_i17.CourseProgress>> getCourseProgress(int? id) =>
_i9.Future<List<_i23.CourseProgress>> getCourseProgress(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCourseProgress,
[id],
),
returnValue: _i9.Future<List<_i17.CourseProgress>>.value(
<_i17.CourseProgress>[]),
returnValueForMissingStub: _i9.Future<List<_i17.CourseProgress>>.value(
<_i17.CourseProgress>[]),
) as _i9.Future<List<_i17.CourseProgress>>);
returnValue: _i9.Future<List<_i23.CourseProgress>>.value(
<_i23.CourseProgress>[]),
returnValueForMissingStub: _i9.Future<List<_i23.CourseProgress>>.value(
<_i23.CourseProgress>[]),
) as _i9.Future<List<_i23.CourseProgress>>);
@override
_i9.Future<List<_i18.CourseLesson>> getCourseLessons(int? id) =>
_i9.Future<List<_i24.CourseLesson>> getCourseLessons(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCourseLessons,
[id],
),
returnValue:
_i9.Future<List<_i18.CourseLesson>>.value(<_i18.CourseLesson>[]),
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i18.CourseLesson>>.value(<_i18.CourseLesson>[]),
) as _i9.Future<List<_i18.CourseLesson>>);
_i9.Future<List<_i24.CourseLesson>>.value(<_i24.CourseLesson>[]),
) as _i9.Future<List<_i24.CourseLesson>>);
@override
_i9.Future<Map<String, dynamic>> completeLesson(int? id) =>
@ -1191,30 +1302,28 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
) as _i9.Future<Map<String, dynamic>>);
@override
_i9.Future<List<_i19.Practice>> getCoursePractices(int? id) =>
_i9.Future<List<_i25.Practice>> getCoursePractices(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursePractices,
[id],
),
returnValue: _i9.Future<List<_i19.Practice>>.value(<_i19.Practice>[]),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
returnValueForMissingStub:
_i9.Future<List<_i19.Practice>>.value(<_i19.Practice>[]),
) as _i9.Future<List<_i19.Practice>>);
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
) as _i9.Future<List<_i25.Practice>>);
@override
_i9.Future<List<_i20.PracticeQuestion>> getCoursePracticeQuestions(int? id) =>
_i9.Future<List<_i14.Question>> getCoursePracticeQuestions(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursePracticeQuestions,
[id],
),
returnValue: _i9.Future<List<_i20.PracticeQuestion>>.value(
<_i20.PracticeQuestion>[]),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
returnValueForMissingStub:
_i9.Future<List<_i20.PracticeQuestion>>.value(
<_i20.PracticeQuestion>[]),
) as _i9.Future<List<_i20.PracticeQuestion>>);
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
) as _i9.Future<List<_i14.Question>>);
@override
_i9.Future<_i14.Question?> getCoursePracticeQuestion(int? id) =>
@ -1228,73 +1337,95 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
) as _i9.Future<_i14.Question?>);
@override
_i9.Future<List<_i16.Subcategory>> getLearnSubcategories() =>
_i9.Future<List<_i22.Subcategory>> getLearnSubcategories() =>
(super.noSuchMethod(
Invocation.method(
#getLearnSubcategories,
[],
),
returnValue:
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
returnValueForMissingStub:
_i9.Future<List<_i16.Subcategory>>.value(<_i16.Subcategory>[]),
) as _i9.Future<List<_i16.Subcategory>>);
_i9.Future<List<_i22.Subcategory>>.value(<_i22.Subcategory>[]),
) as _i9.Future<List<_i22.Subcategory>>);
@override
_i9.Future<List<_i21.Course>> getCourses(int? id) => (super.noSuchMethod(
_i9.Future<List<_i26.Course>> getCourses(int? id) => (super.noSuchMethod(
Invocation.method(
#getCourses,
[id],
),
returnValue: _i9.Future<List<_i21.Course>>.value(<_i21.Course>[]),
returnValue: _i9.Future<List<_i26.Course>>.value(<_i26.Course>[]),
returnValueForMissingStub:
_i9.Future<List<_i21.Course>>.value(<_i21.Course>[]),
) as _i9.Future<List<_i21.Course>>);
_i9.Future<List<_i26.Course>>.value(<_i26.Course>[]),
) as _i9.Future<List<_i26.Course>>);
@override
_i9.Future<List<_i22.Level>> getLevels(int? id) => (super.noSuchMethod(
_i9.Future<List<_i27.Level>> getLevels(int? id) => (super.noSuchMethod(
Invocation.method(
#getLevels,
[id],
),
returnValue: _i9.Future<List<_i22.Level>>.value(<_i22.Level>[]),
returnValue: _i9.Future<List<_i27.Level>>.value(<_i27.Level>[]),
returnValueForMissingStub:
_i9.Future<List<_i22.Level>>.value(<_i22.Level>[]),
) as _i9.Future<List<_i22.Level>>);
_i9.Future<List<_i27.Level>>.value(<_i27.Level>[]),
) as _i9.Future<List<_i27.Level>>);
@override
_i9.Future<List<_i23.Module>> getModules(int? id) => (super.noSuchMethod(
_i9.Future<List<_i28.Module>> getModules(int? id) => (super.noSuchMethod(
Invocation.method(
#getModules,
[id],
),
returnValue: _i9.Future<List<_i23.Module>>.value(<_i23.Module>[]),
returnValue: _i9.Future<List<_i28.Module>>.value(<_i28.Module>[]),
returnValueForMissingStub:
_i9.Future<List<_i23.Module>>.value(<_i23.Module>[]),
) as _i9.Future<List<_i23.Module>>);
_i9.Future<List<_i28.Module>>.value(<_i28.Module>[]),
) as _i9.Future<List<_i28.Module>>);
@override
_i9.Future<List<_i24.Submodule>> getSubmodules(int? id) =>
_i9.Future<List<_i29.Submodule>> getSubmodules(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubmodules,
[id],
),
returnValue: _i9.Future<List<_i24.Submodule>>.value(<_i24.Submodule>[]),
returnValue: _i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]),
returnValueForMissingStub:
_i9.Future<List<_i24.Submodule>>.value(<_i24.Submodule>[]),
) as _i9.Future<List<_i24.Submodule>>);
_i9.Future<List<_i29.Submodule>>.value(<_i29.Submodule>[]),
) as _i9.Future<List<_i29.Submodule>>);
@override
_i9.Future<List<_i25.Lesson>> getLessons(int? id) => (super.noSuchMethod(
_i9.Future<List<_i30.Lesson>> getLessons(int? id) => (super.noSuchMethod(
Invocation.method(
#getLessons,
[id],
),
returnValue: _i9.Future<List<_i25.Lesson>>.value(<_i25.Lesson>[]),
returnValue: _i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i25.Lesson>>.value(<_i25.Lesson>[]),
) as _i9.Future<List<_i25.Lesson>>);
_i9.Future<List<_i30.Lesson>>.value(<_i30.Lesson>[]),
) as _i9.Future<List<_i30.Lesson>>);
@override
_i9.Future<List<_i25.Practice>> getPractices(int? id) => (super.noSuchMethod(
Invocation.method(
#getPractices,
[id],
),
returnValue: _i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
returnValueForMissingStub:
_i9.Future<List<_i25.Practice>>.value(<_i25.Practice>[]),
) as _i9.Future<List<_i25.Practice>>);
@override
_i9.Future<List<_i14.Question>> getQuestions(int? id) => (super.noSuchMethod(
Invocation.method(
#getQuestions,
[id],
),
returnValue: _i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
returnValueForMissingStub:
_i9.Future<List<_i14.Question>>.value(<_i14.Question>[]),
) as _i9.Future<List<_i14.Question>>);
}
/// A class which mocks [SecureStorageService].
@ -1397,7 +1528,7 @@ class MockSecureStorageService extends _i1.Mock
/// A class which mocks [DioService].
///
/// See the documentation for Mockito's code generation for more information.
class MockDioService extends _i1.Mock implements _i26.DioService {
class MockDioService extends _i1.Mock implements _i31.DioService {
@override
_i2.Dio get dio => (super.noSuchMethod(
Invocation.getter(#dio),
@ -1416,7 +1547,7 @@ class MockDioService extends _i1.Mock implements _i26.DioService {
///
/// See the documentation for Mockito's code generation for more information.
class MockStatusCheckerService extends _i1.Mock
implements _i27.StatusCheckerService {
implements _i32.StatusCheckerService {
@override
_i3.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage),
@ -1437,16 +1568,6 @@ class MockStatusCheckerService extends _i1.Mock
returnValueForMissingStub: false,
) as bool);
@override
_i9.Future<int> getBatteryLevel() => (super.noSuchMethod(
Invocation.method(
#getBatteryLevel,
[],
),
returnValue: _i9.Future<int>.value(0),
returnValueForMissingStub: _i9.Future<int>.value(0),
) as _i9.Future<int>);
@override
_i9.Future<bool> checkConnection() => (super.noSuchMethod(
Invocation.method(
@ -1456,66 +1577,46 @@ class MockStatusCheckerService extends _i1.Mock
returnValue: _i9.Future<bool>.value(false),
returnValueForMissingStub: _i9.Future<bool>.value(false),
) as _i9.Future<bool>);
@override
_i9.Future<int> getAvailableStorage() => (super.noSuchMethod(
Invocation.method(
#getAvailableStorage,
[],
),
returnValue: _i9.Future<int>.value(0),
returnValueForMissingStub: _i9.Future<int>.value(0),
) as _i9.Future<int>);
@override
_i9.Future<void> checkAndUpdate() => (super.noSuchMethod(
Invocation.method(
#checkAndUpdate,
[],
),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
}
/// A class which mocks [PermissionHandlerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockPermissionHandlerService extends _i1.Mock
implements _i28.PermissionHandlerService {
implements _i33.PermissionHandlerService {
@override
_i9.Future<_i29.PermissionStatus> requestPermission(
_i29.Permission? requestedPermission) =>
_i9.Future<_i34.PermissionStatus> requestPermission(
_i34.Permission? requestedPermission) =>
(super.noSuchMethod(
Invocation.method(
#requestPermission,
[requestedPermission],
),
returnValue: _i9.Future<_i29.PermissionStatus>.value(
_i29.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i29.PermissionStatus>.value(
_i29.PermissionStatus.denied),
) as _i9.Future<_i29.PermissionStatus>);
returnValue: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>);
@override
_i9.Future<_i29.PermissionStatus> request(_i29.Permission? permission) =>
_i9.Future<_i34.PermissionStatus> request(_i34.Permission? permission) =>
(super.noSuchMethod(
Invocation.method(
#request,
[permission],
),
returnValue: _i9.Future<_i29.PermissionStatus>.value(
_i29.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i29.PermissionStatus>.value(
_i29.PermissionStatus.denied),
) as _i9.Future<_i29.PermissionStatus>);
returnValue: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i34.PermissionStatus>.value(
_i34.PermissionStatus.denied),
) as _i9.Future<_i34.PermissionStatus>);
}
/// A class which mocks [ImagePickerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockImagePickerService extends _i1.Mock
implements _i30.ImagePickerService {
implements _i35.ImagePickerService {
@override
_i9.Future<String?> gallery() => (super.noSuchMethod(
Invocation.method(
@ -1540,7 +1641,7 @@ class MockImagePickerService extends _i1.Mock
/// A class which mocks [GoogleAuthService].
///
/// See the documentation for Mockito's code generation for more information.
class MockGoogleAuthService extends _i1.Mock implements _i31.GoogleAuthService {
class MockGoogleAuthService extends _i1.Mock implements _i36.GoogleAuthService {
@override
int get listenersCount => (super.noSuchMethod(
Invocation.getter(#listenersCount),
@ -1610,7 +1711,7 @@ class MockGoogleAuthService extends _i1.Mock implements _i31.GoogleAuthService {
///
/// See the documentation for Mockito's code generation for more information.
class MockImageDownloaderService extends _i1.Mock
implements _i32.ImageDownloaderService {
implements _i37.ImageDownloaderService {
@override
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
Invocation.method(
@ -1639,7 +1740,7 @@ class MockImageDownloaderService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockNotificationService extends _i1.Mock
implements _i33.NotificationService {
implements _i38.NotificationService {
@override
_i9.Future<void> initialize() => (super.noSuchMethod(
Invocation.method(
@ -1661,7 +1762,7 @@ class MockNotificationService extends _i1.Mock
) as _i9.Future<void>);
@override
_i9.Future<void> showNotification(_i34.RemoteMessage? message) =>
_i9.Future<void> showNotification(_i39.RemoteMessage? message) =>
(super.noSuchMethod(
Invocation.method(
#showNotification,
@ -1695,7 +1796,7 @@ class MockNotificationService extends _i1.Mock
/// A class which mocks [SmartAuthService].
///
/// See the documentation for Mockito's code generation for more information.
class MockSmartAuthService extends _i1.Mock implements _i35.SmartAuthService {
class MockSmartAuthService extends _i1.Mock implements _i40.SmartAuthService {
@override
bool get listenForMultipleSms => (super.noSuchMethod(
Invocation.getter(#listenForMultipleSms),
@ -1727,26 +1828,26 @@ class MockSmartAuthService extends _i1.Mock implements _i35.SmartAuthService {
/// A class which mocks [CourseService].
///
/// See the documentation for Mockito's code generation for more information.
class MockCourseService extends _i1.Mock implements _i36.CourseService {
class MockCourseService extends _i1.Mock implements _i41.CourseService {
@override
_i9.Future<List<_i37.CourseDetail>> getCoursesDetail(int? id) =>
_i9.Future<List<_i42.CourseDetail>> getCoursesDetail(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursesDetail,
[id],
),
returnValue:
_i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]),
returnValueForMissingStub:
_i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
) as _i9.Future<List<_i37.CourseDetail>>);
_i9.Future<List<_i42.CourseDetail>>.value(<_i42.CourseDetail>[]),
) as _i9.Future<List<_i42.CourseDetail>>);
}
/// A class which mocks [AudioPlayerService].
///
/// See the documentation for Mockito's code generation for more information.
class MockAudioPlayerService extends _i1.Mock
implements _i38.AudioPlayerService {
implements _i43.AudioPlayerService {
@override
_i4.AudioPlayer get player => (super.noSuchMethod(
Invocation.getter(#player),
@ -1870,13 +1971,13 @@ class MockAudioPlayerService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockVoiceRecorderService extends _i1.Mock
implements _i39.VoiceRecorderService {
implements _i44.VoiceRecorderService {
@override
_i40.VoiceRecordingState get recordingState => (super.noSuchMethod(
_i45.VoiceRecordingState get recordingState => (super.noSuchMethod(
Invocation.getter(#recordingState),
returnValue: _i40.VoiceRecordingState.pending,
returnValueForMissingStub: _i40.VoiceRecordingState.pending,
) as _i40.VoiceRecordingState);
returnValue: _i45.VoiceRecordingState.pending,
returnValueForMissingStub: _i45.VoiceRecordingState.pending,
) as _i45.VoiceRecordingState);
@override
_i5.WaveformRecorderController get waveController => (super.noSuchMethod(
@ -1965,3 +2066,39 @@ class MockVoiceRecorderService extends _i1.Mock
returnValueForMissingStub: null,
);
}
/// A class which mocks [InAppUpdateService].
///
/// See the documentation for Mockito's code generation for more information.
class MockInAppUpdateService extends _i1.Mock
implements _i46.InAppUpdateService {
@override
_i9.Future<int> getBatteryLevel() => (super.noSuchMethod(
Invocation.method(
#getBatteryLevel,
[],
),
returnValue: _i9.Future<int>.value(0),
returnValueForMissingStub: _i9.Future<int>.value(0),
) as _i9.Future<int>);
@override
_i9.Future<int> getAvailableStorage() => (super.noSuchMethod(
Invocation.method(
#getAvailableStorage,
[],
),
returnValue: _i9.Future<int>.value(0),
returnValueForMissingStub: _i9.Future<int>.value(0),
) as _i9.Future<int>);
@override
_i9.Future<void> checkForUpdate() => (super.noSuchMethod(
Invocation.method(
#checkForUpdate,
[],
),
returnValue: _i9.Future<void>.value(),
returnValueForMissingStub: _i9.Future<void>.value(),
) as _i9.Future<void>);
}

View File

@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('LearnViewModel Tests -', () {
group('InAppUpdateServiceTest -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});

View File

@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('LearnLevelViewModel Tests -', () {
group('LearnCourseViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});

View File

@ -4,7 +4,7 @@ import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('LearnSubmoduleViewModel Tests -', () {
group('LearnProgramViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});

View File

@ -1,11 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:yimaru_app/app/app.locator.dart';
import '../helpers/test_helpers.dart';
void main() {
group('LearnSubcategoryViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}