Merge branch 'release/1.0.3'

-fix(learn): Integrate learn lessons according to the new hierarchy.
This commit is contained in:
BisratHailu 2026-04-20 16:01:44 +03:00
commit ee981ceba9
36 changed files with 1235 additions and 548 deletions

File diff suppressed because one or more lines are too long

View File

@ -52,6 +52,7 @@ import 'package:yimaru_app/services/audio_player_service.dart';
import 'package:yimaru_app/services/voice_recorder_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/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_subcategory/learn_subcategory_view.dart';
import 'package:yimaru_app/ui/views/learn_submodule/learn_submodule_view.dart';
// @stacked-import // @stacked-import
@StackedApp( @StackedApp(
@ -92,6 +93,7 @@ import 'package:yimaru_app/ui/views/learn_subcategory/learn_subcategory_view.dar
MaterialRoute(page: CourseView), MaterialRoute(page: CourseView),
MaterialRoute(page: CoursePracticeQuestionView), MaterialRoute(page: CoursePracticeQuestionView),
MaterialRoute(page: LearnSubcategoryView), MaterialRoute(page: LearnSubcategoryView),
MaterialRoute(page: LearnSubmoduleView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [

File diff suppressed because it is too large Load Diff

52
lib/models/lesson.dart Normal file
View File

@ -0,0 +1,52 @@
import 'package:json_annotation/json_annotation.dart';
part 'lesson.g.dart';
@JsonSerializable()
class Lesson {
final int? id;
final String? title;
final String? thumbnail;
final String? description;
@JsonKey(name: 'is_active')
final bool? isActive;
@JsonKey(name: 'sub_module_id')
final int? subModuleId;
@JsonKey(name: 'teaching_text')
final String? teachingText;
@JsonKey(name: 'display_order')
final int? displayOrder;
@JsonKey(name: 'teaching_video_url')
final String? teachingVideoUrl;
@JsonKey(name: 'teaching_image_url')
final String? teachingImageUrl;
@JsonKey(name: 'teaching_audio_url')
final String? teachingAudioUrl;
const Lesson(
{this.id,
this.title,
this.isActive,
this.thumbnail,
this.subModuleId,
this.description,
this.teachingText,
this.displayOrder,
this.teachingAudioUrl,
this.teachingImageUrl,
this.teachingVideoUrl});
factory Lesson.fromJson(Map<String, dynamic> json) => _$LessonFromJson(json);
Map<String, dynamic> toJson() => _$LessonToJson(this);
}

35
lib/models/lesson.g.dart Normal file
View File

@ -0,0 +1,35 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'lesson.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Lesson _$LessonFromJson(Map<String, dynamic> json) => Lesson(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
isActive: json['is_active'] as bool?,
thumbnail: json['thumbnail'] as String?,
subModuleId: (json['sub_module_id'] as num?)?.toInt(),
description: json['description'] as String?,
teachingText: json['teaching_text'] as String?,
displayOrder: (json['display_order'] as num?)?.toInt(),
teachingAudioUrl: json['teaching_audio_url'] as String?,
teachingImageUrl: json['teaching_image_url'] as String?,
teachingVideoUrl: json['teaching_video_url'] as String?,
);
Map<String, dynamic> _$LessonToJson(Lesson instance) => <String, dynamic>{
'id': instance.id,
'title': instance.title,
'thumbnail': instance.thumbnail,
'description': instance.description,
'is_active': instance.isActive,
'sub_module_id': instance.subModuleId,
'teaching_text': instance.teachingText,
'display_order': instance.displayOrder,
'teaching_video_url': instance.teachingVideoUrl,
'teaching_image_url': instance.teachingImageUrl,
'teaching_audio_url': instance.teachingAudioUrl,
};

44
lib/models/submodule.dart Normal file
View File

@ -0,0 +1,44 @@
import 'package:json_annotation/json_annotation.dart';
part 'submodule.g.dart';
@JsonSerializable()
class Submodule {
final int? id;
final String? tips;
final String? title;
final String? thumbnail;
final String? description;
@JsonKey(name: 'module_id')
final int? moduleId;
@JsonKey(name: 'is_active')
final bool? isActive;
@JsonKey(name: 'display_order')
final int? displayOrder;
@JsonKey(name: 'legacy_sub_course_id')
final int? legacySubCourseId;
const Submodule(
{this.id,
this.title,
this.tips,
this.moduleId,
this.isActive,
this.thumbnail,
this.description,
this.displayOrder,
this.legacySubCourseId});
factory Submodule.fromJson(Map<String, dynamic> json) =>
_$SubmoduleFromJson(json);
Map<String, dynamic> toJson() => _$SubmoduleToJson(this);
}

View File

@ -0,0 +1,31 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'submodule.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Submodule _$SubmoduleFromJson(Map<String, dynamic> json) => Submodule(
id: (json['id'] as num?)?.toInt(),
title: json['title'] as String?,
tips: json['tips'] as String?,
moduleId: (json['module_id'] as num?)?.toInt(),
isActive: json['is_active'] as bool?,
thumbnail: json['thumbnail'] as String?,
description: json['description'] as String?,
displayOrder: (json['display_order'] as num?)?.toInt(),
legacySubCourseId: (json['legacy_sub_course_id'] as num?)?.toInt(),
);
Map<String, dynamic> _$SubmoduleToJson(Submodule instance) => <String, dynamic>{
'id': instance.id,
'tips': instance.tips,
'title': instance.title,
'thumbnail': instance.thumbnail,
'description': instance.description,
'module_id': instance.moduleId,
'is_active': instance.isActive,
'display_order': instance.displayOrder,
'legacy_sub_course_id': instance.legacySubCourseId,
};

View File

@ -13,7 +13,9 @@ import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/ui/common/app_constants.dart'; import 'package:yimaru_app/ui/common/app_constants.dart';
import '../app/app.locator.dart'; import '../app/app.locator.dart';
import '../models/lesson.dart';
import '../models/module.dart'; import '../models/module.dart';
import '../models/submodule.dart';
import '../ui/common/enmus.dart'; import '../ui/common/enmus.dart';
class ApiService { class ApiService {
@ -674,4 +676,52 @@ class ApiService {
return []; return [];
} }
} }
// Get submodules
Future<List<Submodule>> getSubmodules(int id) async {
try {
List<Submodule> submodules = [];
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kModulesUrl/$id/$kSubmodulesUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data']['sub_modules'] as List;
submodules = decodedData.map(
(e) {
return Submodule.fromJson(e);
},
).toList();
return submodules;
}
return [];
} catch (e) {
return [];
}
}
// Get lessons
Future<List<Lesson>> getLessons(int id) async {
try {
List<Lesson> lessons = [];
final Response response = await _service.dio.get(
'$kBaseUrl/api/$kApiVersionUrl/$kCourseManagementUrl/$kSubmodulesUrl/$id/$kLessonsUrl');
if (response.statusCode == 200) {
var data = response.data;
var decodedData = data['data'] as List;
lessons = decodedData.map(
(e) {
return Lesson.fromJson(e);
},
).toList();
return lessons;
}
return [];
} catch (e) {
return [];
}
}
} }

View File

@ -10,6 +10,8 @@ String kCoursesUrl = 'courses';
String kModulesUrl = 'modules'; String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons';
String kRegisterUrl = 'register'; String kRegisterUrl = 'register';
String kCategoryUrl = 'categories'; String kCategoryUrl = 'categories';
@ -24,6 +26,8 @@ String kResendOtpUrl = 'resend-otp';
String kGetUserUrl = 'user-profile'; String kGetUserUrl = 'user-profile';
String kSubmodulesUrl = 'sub-modules';
String kSubcoursesUrl = 'sub-courses'; String kSubcoursesUrl = 'sub-courses';
String kCompleteLessonUrl = 'complete'; String kCompleteLessonUrl = 'complete';

View File

@ -28,6 +28,7 @@ enum StateObjects {
verifyOtp, verifyOtp,
resendOtp, resendOtp,
learnLevels, learnLevels,
learnLessons,
learnModules, learnModules,
learnCourses, learnCourses,
profileImage, profileImage,
@ -40,6 +41,7 @@ enum StateObjects {
loginWithGoogle, loginWithGoogle,
loadLessonVideo, loadLessonVideo,
loadCourseVideo, loadCourseVideo,
learnSubmodules,
requestResetCode, requestResetCode,
courseCategories, courseCategories,
profileCompletion, profileCompletion,

View File

@ -41,9 +41,8 @@ class CourseCategoryViewModel extends ReactiveViewModel {
// Remote api call // Remote api call
// Course categories // Course categories
Future<void> getCategories() async => Future<void> getCategories() async => await runBusyFuture(_getCategories(),
await runBusyFuture(_getCategories(), busyObject: StateObjects.courseCategories);
busyObject: StateObjects.courseCategories);
Future<void> _getCategories() async { Future<void> _getCategories() async {
if (categories.isEmpty) { if (categories.isEmpty) {

View File

@ -66,6 +66,7 @@ class CourseLessonDetailViewModel extends BaseViewModel {
busyObject: StateObjects.loadCourseVideo); busyObject: StateObjects.loadCourseVideo);
Future<void> _initializePlayer(CourseLesson lesson) async { Future<void> _initializePlayer(CourseLesson lesson) async {
print('URL: $kSampleVideoUrl');
_videoPlayerController = _videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(kSampleVideoUrl)); VideoPlayerController.networkUrl(Uri.parse(kSampleVideoUrl));

View File

@ -1,31 +1,30 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/submodule.dart';
import 'package:yimaru_app/ui/common/enmus.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/learn_lesson_tile.dart';
import 'package:yimaru_app/ui/widgets/module_progress.dart'; import 'package:yimaru_app/ui/widgets/module_progress.dart';
import 'package:yimaru_app/ui/widgets/motivation_card.dart'; import 'package:yimaru_app/ui/widgets/motivation_card.dart';
import '../../../models/lesson.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../widgets/custom_elevated_button.dart'; import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/small_app_bar.dart'; import '../../widgets/small_app_bar.dart';
import 'learn_lesson_viewmodel.dart'; import 'learn_lesson_viewmodel.dart';
class LearnLessonView extends StackedView<LearnLessonViewModel> { class LearnLessonView extends StackedView<LearnLessonViewModel> {
final String title; final Submodule submodule;
final String topics;
final String subtitle; const LearnLessonView({Key? key, required this.submodule}) : super(key: key);
final String description;
final List<Map<String, dynamic>> practices;
@override
void onViewModelReady(LearnLessonViewModel viewModel) async {
await viewModel.getLessons(submodule.id ?? 0);
super.onViewModelReady(viewModel);
}
const LearnLessonView(
{Key? key,
required this.title,
required this.topics,
required this.subtitle,
required this.practices,
required this.description})
: super(key: key);
Widget getPadding(context) { Widget getPadding(context) {
double half = screenHeight(context) / 2; double half = screenHeight(context) / 2;
@ -117,95 +116,63 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
verticalSpaceTiny, verticalSpaceTiny,
_buildSubtitle(), _buildSubtitle(),
verticalSpaceSmall, verticalSpaceSmall,
_buildTopics(),
verticalSpaceSmall, verticalSpaceSmall,
// _buildModuleProgress(), _buildModuleProgress(),
// verticalSpaceMedium, verticalSpaceMedium,
// _buildContinueButton(), verticalSpaceMedium,
// verticalSpaceMedium, _buildMotivationCard(),
// _buildMotivationCard(), verticalSpaceMedium,
// verticalSpaceMedium, _buildHeader(),
//_buildHeader(), verticalSpaceMedium,
//verticalSpaceMedium, _buildListViewBuilder(viewModel),
// _buildListView(viewModel),
getPadding(context),
_buildStartButton(viewModel),
verticalSpaceSmall,
_buildPracticeButton(viewModel)
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
title, submodule.title ?? '',
style: style16DG600, style: style16DG600,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
subtitle, submodule.description ?? '',
style: style14DG600, style: style14DG600,
); );
Widget _buildTopics() => Text(
topics,
style: style14DG500,
);
Widget _buildModuleProgress() => const ModuleProgress(); Widget _buildModuleProgress() => const ModuleProgress();
Widget _buildStartButton(LearnLessonViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Start $title',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.navigateToLearnLessonDetail(
title: title, practices: practices, description: description),
);
Widget _buildPracticeButton(LearnLessonViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Practice',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
onTap: () async =>
await viewModel.navigateToLearnPractice(practices));
Widget _buildMotivationCard() => const MotivationCard(); Widget _buildMotivationCard() => const MotivationCard();
Widget _buildHeader() => Text( Widget _buildHeader() => Text(
title, 'Lessons in this module',
style: style18DG700, style: style18DG700,
); );
Widget _buildListViewBuilder(LearnLessonViewModel viewModel) =>
viewModel.busy(StateObjects.learnLessons)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder( Widget _buildListView(LearnLessonViewModel viewModel) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.lessons.length, itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
title: viewModel.lessons[index]['title'], lesson: viewModel.lessons[index],
status: viewModel.lessons[index]['status'], onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(viewModel.lessons[index]),
thumbnail: viewModel.lessons[index]['thumbnail'],
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
title: title, practices: practices, description: description),
// onPracticeTap: () async => await viewModel.navigateToLearnPractice(),
), ),
); );
Widget _buildTile({ Widget _buildTile({
required String title, required Lesson lesson,
required String thumbnail, required GestureTapCallback? onLessonTap,
GestureTapCallback? onLessonTap,
required ProgressStatuses status,
GestureTapCallback? onPracticeTap,
}) => }) =>
LearnLessonTile( LearnLessonTile(
title: title, lesson: lesson,
status: status,
thumbnail: thumbnail,
onLessonTap: onLessonTap, onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
); );
} }

View File

@ -4,47 +4,42 @@ import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/lesson.dart';
import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart';
class LearnLessonViewModel extends BaseViewModel { class LearnLessonViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
// Lessons // Learn lessons
final List<Map<String, dynamic>> _lessons = [ List<Lesson> _lessons = [];
{
'title': '1.1 Introducing Yourself',
'status': ProgressStatuses.completed,
'thumbnail': 'assets/images/image_1.png',
},
{
'status': ProgressStatuses.completed,
'thumbnail': 'assets/images/image_1.png',
'title': '1.2 Talking About Your Surroundings',
},
{
'status': ProgressStatuses.pending,
'title': '1.1 Introducing Yourself',
'thumbnail': 'assets/images/image_1.png',
},
];
List<Map<String, dynamic>> get lessons => _lessons; List<Lesson> get lessons => _lessons;
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLessonDetail( Future<void> navigateToLearnLessonDetail(Lesson lesson) async =>
{required String title, await _navigationService.navigateToLearnLessonDetailView(lesson: lesson);
required List<Map<String, dynamic>> practices,
required String description}) async =>
await _navigationService.navigateToLearnLessonDetailView(
title: title, practices: practices, description: description);
Future<void> navigateToLearnPractice( // Remote api call
List<Map<String, dynamic>> practices) async =>
await _navigationService.navigateToLearnPracticeView( // Learn modules
practices: practices, Future<void> getLessons(int id) async => await runBusyFuture(_getLessons(id),
title: 'Lets Practice', busyObject: StateObjects.learnLessons);
buttonLabel: 'Begin Lesson Practice',
subtitle: 'Lets quickly review what youve learned in this lesson!', 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));
}
}
}
} }

View File

@ -1,8 +1,9 @@
import 'package:chewie/chewie.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.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/ui/widgets/empty_video_player.dart';
import '../../../models/lesson.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
@ -11,20 +12,14 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_detail_viewmodel.dart'; import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> { class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
final String title; final Lesson lesson;
final String description;
final List<Map<String, dynamic>> practices;
const LearnLessonDetailView( const LearnLessonDetailView({Key? key, required this.lesson})
{Key? key,
required this.title,
required this.practices,
required this.description})
: super(key: key); : super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async { Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
await viewModel.pause(); await viewModel.pause();
await viewModel.navigateToLearnPractice(practices); // await viewModel.navigateToLearnPractice(practices);
} }
@override @override
@ -34,12 +29,6 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
super.onDispose(viewModel); super.onDispose(viewModel);
} }
@override
void onViewModelReady(LearnLessonDetailViewModel viewModel) async {
await viewModel.initializePlayer();
super.onViewModelReady(viewModel);
}
@override @override
LearnLessonDetailViewModel viewModelBuilder(BuildContext context) => LearnLessonDetailViewModel viewModelBuilder(BuildContext context) =>
LearnLessonDetailViewModel(); LearnLessonDetailViewModel();
@ -125,7 +114,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
title, lesson.title ?? '',
style: style16DG600, style: style16DG600,
); );
@ -134,21 +123,21 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
height: 200, height: 200,
color: kcBlack, color: kcBlack,
width: double.maxFinite, width: double.maxFinite,
child: _buildVideoPlayerState(viewModel), child: _buildVideoPlayer(viewModel),
); );
Widget _buildVideoPlayerState(LearnLessonDetailViewModel viewModel) =>
viewModel.chewieController != null &&
viewModel.videoPlayerController != null &&
!viewModel.busy(StateObjects.loadLessonVideo)
? _buildVideoPlayer(viewModel)
: _buildEmptyVideoPlayer();
Widget _buildVideoPlayer(LearnLessonDetailViewModel viewModel) => Widget _buildVideoPlayer(LearnLessonDetailViewModel viewModel) =>
_buildChewiePlayer(viewModel); _buildVimeoPlayer(viewModel);
Widget _buildChewiePlayer(LearnLessonDetailViewModel viewModel) => Widget _buildVimeoPlayer(LearnLessonDetailViewModel viewModel) =>
Chewie(controller: viewModel.chewieController!); VimeoVideoPlayer(
isAutoPlay: true,
onInAppWebViewCreated: (controller) =>
viewModel.initializePlayer(controller),
videoId: lesson.teachingVideoUrl?.split('/').last ?? '',
);
Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer(); Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer();
@ -158,7 +147,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
); );
Widget _buildDescription() => Text( Widget _buildDescription() => Text(
description, lesson.description ?? '',
style: style14DG600, style: style14DG600,
); );
@ -175,7 +164,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) => Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Practice', text: 'Lessons',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,

View File

@ -1,11 +1,10 @@
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
@ -20,6 +19,10 @@ class LearnLessonDetailViewModel extends BaseViewModel {
ChewieController? get chewieController => _chewieController; ChewieController? get chewieController => _chewieController;
InAppWebViewController? _webViewController;
InAppWebViewController? get webViewController => _webViewController;
VideoPlayerController? _videoPlayerController; VideoPlayerController? _videoPlayerController;
VideoPlayerController? get videoPlayerController => _videoPlayerController; VideoPlayerController? get videoPlayerController => _videoPlayerController;
@ -34,32 +37,22 @@ class LearnLessonDetailViewModel extends BaseViewModel {
await _chewieController?.pause(); await _chewieController?.pause();
} }
Future<void> initializePlayer() async => void initializePlayer(InAppWebViewController controller){
await runBusyFuture(_initializePlayer(), _webViewController = controller;
busyObject: StateObjects.loadLessonVideo); rebuildUi();
Future<void> _initializePlayer() async {
_videoPlayerController =
VideoPlayerController.networkUrl(Uri.parse(kSampleVideoUrl));
await _videoPlayerController?.initialize();
if (_videoPlayerController != null) {
_chewieController = ChewieController(
looping: true,
autoPlay: true,
showOptions: true,
showControls: true,
aspectRatio: 16 / 9,
autoInitialize: true,
allowedScreenSleep: false,
videoPlayerController: _videoPlayerController!,
materialProgressColors: buildChewieProgressIndicator);
}
// rebuildUi();
} }
void onLoadVideoStart() {
setBusyForObject(StateObjects.loadLessonVideo, true);
rebuildUi();
}
void onLoadVideoComplete() {
setBusyForObject(StateObjects.loadLessonVideo, false);
rebuildUi();
}
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();

View File

@ -55,6 +55,7 @@ class LearnLevelView extends StackedView<LearnLevelViewModel> {
); );
Widget _buildAppBar(LearnLevelViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLevelViewModel viewModel) => SmallAppBar(
title: 'Levels',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true, showBackButton: true,
); );
@ -82,7 +83,8 @@ class LearnLevelView extends StackedView<LearnLevelViewModel> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
level: viewModel.levels[index], level: viewModel.levels[index],
onTap: () async => await viewModel.navigateToModule( viewModel.levels[index]), onTap: () async =>
await viewModel.navigateToModule(viewModel.levels[index]),
), ),
separatorBuilder: (context, index) => verticalSpaceSmall, separatorBuilder: (context, index) => verticalSpaceSmall,
); );

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/models/level.dart'; import 'package:yimaru_app/models/level.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/widgets/learn_module_tile.dart'; import 'package:yimaru_app/ui/widgets/learn_module_tile.dart';
import 'package:yimaru_app/ui/widgets/overall_learn_progress.dart'; import 'package:yimaru_app/ui/widgets/overall_learn_progress.dart';
import '../../../models/module.dart'; import '../../../models/module.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/enmus.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
import '../../widgets/custom_circular_progress_indicator.dart';
import '../../widgets/small_app_bar.dart'; import '../../widgets/small_app_bar.dart';
import 'learn_module_viewmodel.dart'; import 'learn_module_viewmodel.dart';
@ -57,6 +58,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
); );
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
title: 'Modules',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true, showBackButton: true,
); );
@ -79,10 +81,10 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
_buildSubtitle(), _buildSubtitle(),
verticalSpaceMedium, verticalSpaceLarge,
_buildOverallProgress(), _buildOverallProgress(),
verticalSpaceMedium, verticalSpaceMedium,
_buildListView(viewModel) _buildListViewBuilder(viewModel)
]; ];
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
@ -95,26 +97,36 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
style: style14DG400, style: style14DG400,
); );
Widget _buildOverallProgress() => const OverallLearnProgress(); Widget _buildOverallProgress() => OverallLearnProgress(
color: kcPrimaryColor.withOpacity(0.1),
);
Widget _buildListViewBuilder(LearnModuleViewModel viewModel) =>
viewModel.busy(StateObjects.learnModules)
? _buildProgressIndicator()
: _buildListView(viewModel);
Widget _buildProgressIndicator() => const Center(
child: CustomCircularProgressIndicator(color: kcPrimaryColor),
);
Widget _buildListView(LearnModuleViewModel viewModel) => ListView.builder( Widget _buildListView(LearnModuleViewModel viewModel) => ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: viewModel.modules.length, itemCount: viewModel.modules.length,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index], module: viewModel.modules[index],
onLessonTap: () {}, onModuleTap: () async => await viewModel
onPracticeTap: () {}), .navigateToLearnSubmodule(viewModel.modules[index]),
),
); );
Widget _buildTile({ Widget _buildTile({
required Module module, required Module module,
required GestureTapCallback onLessonTap, required GestureTapCallback onModuleTap,
required GestureTapCallback onPracticeTap,
}) => }) =>
LearnModuleTile( LearnModuleTile(
module: module, module: module,
onLessonTap: onLessonTap, onModuleTap: onModuleTap,
onPracticeTap: onPracticeTap,
); );
} }

View File

@ -24,27 +24,8 @@ class LearnModuleViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLesson( Future<void> navigateToLearnSubmodule(Module module) async =>
{required String title, await _navigationService.navigateToLearnSubmoduleView(module: module);
required String topics,
required String subtitle,
required String description,
required List<Map<String, dynamic>> practices}) async =>
await _navigationService.navigateToLearnLessonView(
title: title,
topics: topics,
subtitle: subtitle,
practices: practices,
description: description);
Future<void> navigateToLearnPractice(
List<Map<String, dynamic>> practices) async =>
await _navigationService.navigateToLearnPracticeView(
practices: practices,
title: 'Lets Practice',
buttonLabel: 'Begin Lesson Practice',
subtitle: 'Lets quickly review what youve learned in this lesson!',
);
// Remote api call // Remote api call

View File

@ -0,0 +1,146 @@
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

@ -0,0 +1,46 @@
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

@ -8,7 +8,7 @@ class CourseModuleBanner extends StatelessWidget {
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container( Widget _buildContainer() => Container(
height: 150, height: 125,
width: double.maxFinite, width: double.maxFinite,
padding: const EdgeInsets.all(15), padding: const EdgeInsets.all(15),
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -1,27 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yimaru_app/models/lesson.dart';
import 'package:yimaru_app/ui/widgets/mini_thumbnail.dart'; import 'package:yimaru_app/ui/widgets/mini_thumbnail.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
import '../common/enmus.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart'; import 'custom_elevated_button.dart';
import 'custom_linear_progress_indicator.dart'; import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends StatelessWidget { class LearnLessonTile extends StatelessWidget {
final String title; final Lesson lesson;
final String thumbnail;
final ProgressStatuses status;
final GestureTapCallback? onLessonTap; final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
const LearnLessonTile({ const LearnLessonTile({super.key, this.onLessonTap, required this.lesson});
super.key,
this.onLessonTap,
this.onPracticeTap,
required this.title,
required this.status,
required this.thumbnail,
});
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();
@ -32,50 +22,55 @@ class LearnLessonTile extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5), borderRadius: BorderRadius.circular(5),
border: Border.all( border: Border.all(
color: ProgressStatuses.pending == status color: kcPrimaryColor.withOpacity(0.1),
? kcPrimaryColor.withOpacity(0.1) // color: ProgressStatuses.pending == status
: kcGreen.withOpacity(0.1), // ? kcPrimaryColor.withOpacity(0.1)
// : kcGreen.withOpacity(0.1),
), ),
), ),
child: _buildExpansionTile(), child: _buildExpansionTile(),
); );
Widget _buildExpansionTile() => ExpansionTile( Widget _buildExpansionTile() => ExpansionTile(
enabled: true,
title: _buildTitle(), title: _buildTitle(),
textColor: kcDarkGrey, textColor: kcDarkGrey,
showTrailingIcon: true, showTrailingIcon: true,
trailing: _buildIconState(), initiallyExpanded: true,
// subtitle: _buildContent(), trailing: _buildPendingIcon(),
collapsedIconColor: kcDarkGrey, collapsedIconColor: kcDarkGrey,
collapsedTextColor: kcDarkGrey, collapsedTextColor: kcDarkGrey,
leading: _buildLeadingWrapper(), leading: _buildLeadingWrapper(),
shape: Border.all(color: kcTransparent), shape: Border.all(color: kcTransparent),
expandedAlignment: Alignment.centerLeft, expandedAlignment: Alignment.centerLeft,
enabled: status != ProgressStatuses.pending, backgroundColor: kcGreen.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing, controlAffinity: ListTileControlAffinity.trailing,
backgroundColor: ProgressStatuses.pending == status
? kcPrimaryColor.withOpacity(0.1)
: kcGreen.withOpacity(0.1),
childrenPadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
expandedCrossAxisAlignment: CrossAxisAlignment.start, expandedCrossAxisAlignment: CrossAxisAlignment.start,
collapsedBackgroundColor: ProgressStatuses.pending == status collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
? kcPrimaryColor.withOpacity(0.1) childrenPadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
: kcGreen.withOpacity(0.1),
tilePadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15), tilePadding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
initiallyExpanded: status != ProgressStatuses.completed ? true : false, // enabled: status != ProgressStatuses.pending,
// backgroundColor: ProgressStatuses.pending == status
// ? kcPrimaryColor.withOpacity(0.1)
// : kcGreen.withOpacity(0.1),
// collapsedBackgroundColor: ProgressStatuses.pending == status
// ? kcPrimaryColor.withOpacity(0.1)
// : kcGreen.withOpacity(0.1),
// initiallyExpanded: status != ProgressStatuses.completed ? true : false,
children: _buildExpansionTileChildren(), children: _buildExpansionTileChildren(),
); );
Widget _buildLeadingWrapper() => MiniThumbnail(thumbnail: thumbnail); Widget _buildLeadingWrapper() =>
MiniThumbnail(thumbnail: lesson.thumbnail ?? 'assets/images/image_1.png');
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
title, lesson.title ?? '',
style: style16DG600, style: style16DG600,
); );
Widget _buildIconState() => ProgressStatuses.pending == status // Widget _buildIconState() => ProgressStatuses.pending == status
? _buildPendingIcon() // ? _buildPendingIcon()
: _buildCompleteIcon(); // : _buildCompleteIcon();
Widget _buildCompleteIcon() => const Icon( Widget _buildCompleteIcon() => const Icon(
Icons.check, Icons.check,
@ -98,58 +93,35 @@ class LearnLessonTile extends StatelessWidget {
List<Widget> _buildExpansionTileItemChildren() => [ List<Widget> _buildExpansionTileItemChildren() => [
_buildProgress(), _buildProgress(),
horizontalSpaceSmall, horizontalSpaceSmall,
_buildProgressText(), // _buildProgressText(),
verticalSpaceSmall, // verticalSpaceSmall,
_buildActionButtonWrapper() _buildActionButtonWrapper()
]; ];
Widget _buildProgress() => CustomLinearProgressIndicator( Widget _buildProgress() => const CustomLinearProgressIndicator(
progress: 0,
activeColor: kcPrimaryColor, activeColor: kcPrimaryColor,
backgroundColor: kcVeryLightGrey, backgroundColor: kcVeryLightGrey,
progress: ProgressStatuses.completed == status ? 1 : 0.75,
); );
Widget _buildProgressText() => Text( // Widget _buildProgressText() => Text(
ProgressStatuses.completed == status ? 'Completed' : 'In Progress', // ProgressStatuses.completed == status ? 'Completed' : 'In Progress',
style: style14P400, // style: style14P400,
); // );
Widget _buildActionButtonWrapper() => SizedBox( Widget _buildActionButtonWrapper() => SizedBox(
height: 40, height: 50,
child: _buildActionButtons(), child: _buildLessonButton(),
);
Widget _buildActionButtons() => Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _buildActionButtonChildren(),
);
List<Widget> _buildActionButtonChildren() => [
_buildPracticeButton(),
horizontalSpaceSmall,
_buildLessonButton(),
];
Widget _buildPracticeButton() => CustomElevatedButton(
height: 15,
width: 135,
text: 'Practice',
borderRadius: 12,
onTap: onPracticeTap,
trailingIcon: Icons.mic,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
); );
Widget _buildLessonButton() => CustomElevatedButton( Widget _buildLessonButton() => CustomElevatedButton(
height: 15, height: 15,
width: 135, text: 'Start',
borderRadius: 12, borderRadius: 12,
onTap: onLessonTap, onTap: onLessonTap,
width: double.maxFinite,
foregroundColor: kcWhite, foregroundColor: kcWhite,
trailingIcon: Icons.play_arrow, trailingIcon: Icons.play_arrow,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
text: ProgressStatuses.completed == status ? 'View' : 'Continue',
); );
} }

View File

@ -6,17 +6,14 @@ import 'package:yimaru_app/ui/widgets/finish_practice_sheet.dart';
import '../../models/module.dart'; import '../../models/module.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
import '../common/enmus.dart';
import '../common/ui_helpers.dart'; import '../common/ui_helpers.dart';
import 'custom_elevated_button.dart'; import 'custom_elevated_button.dart';
class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> { class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
final Module module; final Module module;
final GestureTapCallback? onLessonTap; final GestureTapCallback? onModuleTap;
final GestureTapCallback? onPracticeTap;
const LearnModuleTile( const LearnModuleTile({super.key, this.onModuleTap, required this.module});
{super.key, this.onLessonTap, this.onPracticeTap, required this.module});
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
@ -158,7 +155,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
required LearnModuleViewModel viewModel}) => required LearnModuleViewModel viewModel}) =>
SizedBox( SizedBox(
height: 40, height: 40,
child: _buildActionButtons(context: context, viewModel: viewModel), child: _buildModuleButton(viewModel),
); );
Widget _buildActionButtons( Widget _buildActionButtons(
@ -166,46 +163,39 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
required LearnModuleViewModel viewModel}) => required LearnModuleViewModel viewModel}) =>
Row( Row(
children: [ children: [
_buildLessonButtonWrapper(viewModel), _buildModuleButtonWrapper(viewModel),
horizontalSpaceSmall, horizontalSpaceSmall,
_buildPracticeButtonWrapper(context: context, viewModel: viewModel) _buildPracticeButtonWrapper(context: context, viewModel: viewModel)
], ],
); );
Widget _buildLessonButtonWrapper(LearnModuleViewModel viewModel) => Expanded( Widget _buildModuleButtonWrapper(LearnModuleViewModel viewModel) => Expanded(
child: _buildLessonButton(viewModel), child: _buildModuleButton(viewModel),
); );
Widget _buildLessonButton(LearnModuleViewModel viewModel) => Widget _buildModuleButton(LearnModuleViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 15, height: 15,
borderRadius: 12, borderRadius: 12,
onTap: onLessonTap, onTap: onModuleTap,
text: 'View Module', text: 'View Module',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
// onTap: () async => await viewModel.navigateToLearnLesson(
// title: title,
// topics: topics,
// subtitle: subtitle,
// practices: practices,
// description: description),
); );
Widget _buildPracticeButtonWrapper( Widget _buildPracticeButtonWrapper(
{required BuildContext context, {required BuildContext context,
required LearnModuleViewModel viewModel}) => required LearnModuleViewModel viewModel}) =>
Expanded( Expanded(
child: _buildPracticeButton(context: context, viewModel: viewModel), child: Container(),
); );
Widget _buildPracticeButton( Widget _buildPracticeButton(
{required BuildContext context, {required BuildContext context,
required LearnModuleViewModel viewModel}) => required LearnModuleViewModel viewModel}) =>
CustomElevatedButton( const CustomElevatedButton(
height: 15, height: 15,
borderRadius: 12, borderRadius: 12,
onTap: onPracticeTap,
text: 'View Practices', text: 'View Practices',
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,

View File

@ -0,0 +1,237 @@
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

@ -36,17 +36,17 @@ class ModuleProgress extends StatelessWidget {
[_buildProgressInfo(), _buildProgress()]; [_buildProgressInfo(), _buildProgress()];
Widget _buildProgressInfo() => Text( Widget _buildProgressInfo() => Text(
'60% Progress', '0% Progress',
style: style16DG400, style: style16DG400,
); );
Widget _buildProgress() => Text( Widget _buildProgress() => Text(
'2/3', '0/3',
style: style14P400, style: style14P400,
); );
Widget _buildProgressIndicator() => const CustomLinearProgressIndicator( Widget _buildProgressIndicator() => const CustomLinearProgressIndicator(
progress: 0.75, progress: 0,
activeColor: kcPrimaryColor, activeColor: kcPrimaryColor,
backgroundColor: kcVeryLightGrey, backgroundColor: kcVeryLightGrey,
); );

View File

@ -4,7 +4,8 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart';
class OverallLearnProgress extends StatelessWidget { class OverallLearnProgress extends StatelessWidget {
const OverallLearnProgress({super.key}); final Color color;
const OverallLearnProgress({super.key, required this.color});
@override @override
Widget build(BuildContext context) => _buildContainer(); Widget build(BuildContext context) => _buildContainer();
@ -12,8 +13,8 @@ class OverallLearnProgress extends StatelessWidget {
Widget _buildContainer() => Container( Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25), padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
decoration: BoxDecoration( decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
color: kcPrimaryColor.withOpacity(0.1),
), ),
child: _buildProgressSection(), child: _buildProgressSection(),
); );

View File

@ -8,6 +8,7 @@
#include <audioplayers_linux/audioplayers_linux_plugin.h> #include <audioplayers_linux/audioplayers_linux_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <flutter_inappwebview_linux/flutter_inappwebview_linux_plugin.h>
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> #include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <record_linux/record_linux_plugin.h> #include <record_linux/record_linux_plugin.h>
@ -18,6 +19,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_inappwebview_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterInappwebviewLinuxPlugin");
flutter_inappwebview_linux_plugin_register_with_registrar(flutter_inappwebview_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);

View File

@ -5,6 +5,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
audioplayers_linux audioplayers_linux
file_selector_linux file_selector_linux
flutter_inappwebview_linux
flutter_secure_storage_linux flutter_secure_storage_linux
record_linux record_linux
) )

View File

@ -11,6 +11,7 @@ import connectivity_plus
import file_selector_macos import file_selector_macos
import firebase_core import firebase_core
import firebase_messaging import firebase_messaging
import flutter_inappwebview_macos
import flutter_local_notifications import flutter_local_notifications
import flutter_secure_storage_darwin import flutter_secure_storage_darwin
import google_sign_in_ios import google_sign_in_ios
@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))

View File

@ -558,6 +558,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
flutter_inappwebview:
dependency: "direct main"
description:
name: flutter_inappwebview
sha256: "3952d116ee93bad2946401377e7ade87b5ef200e95ecb5ba1affa1b6329a6867"
url: "https://pub.dev"
source: hosted
version: "6.2.0-beta.3"
flutter_inappwebview_android:
dependency: transitive
description:
name: flutter_inappwebview_android
sha256: "8dfb76bd4e507112c3942c2272eeb01fab2e42be11374e5eb226f58698e7a04b"
url: "https://pub.dev"
source: hosted
version: "1.2.0-beta.3"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
name: flutter_inappwebview_internal_annotations
sha256: e30fba942e3debea7b7e6cdd4f0f59ce89dd403a9865193e3221293b6d1544c6
url: "https://pub.dev"
source: hosted
version: "1.3.0"
flutter_inappwebview_ios:
dependency: transitive
description:
name: flutter_inappwebview_ios
sha256: ae8a78829398771be863aa3c8804a9d40728e1815e66c9c966f86d2cc3ae4fd9
url: "https://pub.dev"
source: hosted
version: "1.2.0-beta.3"
flutter_inappwebview_linux:
dependency: transitive
description:
name: flutter_inappwebview_linux
sha256: "2e1a3b09bb911fb5a8bb155cb7f1eb1428a19b6e20363b9db48beef428b8cef5"
url: "https://pub.dev"
source: hosted
version: "0.1.0-beta.1"
flutter_inappwebview_macos:
dependency: transitive
description:
name: flutter_inappwebview_macos
sha256: "545148cb5c46475ce669ab21621e9f2ad66e05f8e80b2cf49d4018879ab52393"
url: "https://pub.dev"
source: hosted
version: "1.2.0-beta.3"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
name: flutter_inappwebview_platform_interface
sha256: e3522c76e6760d1c0a9ff690e30e1503f226783d3277fa4d26675911977e9766
url: "https://pub.dev"
source: hosted
version: "1.4.0-beta.3"
flutter_inappwebview_web:
dependency: transitive
description:
name: flutter_inappwebview_web
sha256: e98b8875ccb6a3fd255873318db45c18ab135ed0ed22d20169abad9f5c810eb9
url: "https://pub.dev"
source: hosted
version: "1.2.0-beta.3"
flutter_inappwebview_windows:
dependency: transitive
description:
name: flutter_inappwebview_windows
sha256: "902edd6f6326952af822e21aa928f7426d723d45c94c15e6ce3c2d5640d28ad7"
url: "https://pub.dev"
source: hosted
version: "0.7.0-beta.3"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -1741,6 +1813,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.0"
vimeo_video_player:
dependency: "direct main"
description:
name: vimeo_video_player
sha256: b5dc8ad763489c94136e6080ba3ee89830742a48f5e7b2e28968f54d8c3734ad
url: "https://pub.dev"
source: hosted
version: "1.0.3"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:

View File

@ -1,7 +1,7 @@
name: yimaru_app name: yimaru_app
description: A new Flutter project. description: A new Flutter project.
publish_to: 'none' publish_to: 'none'
version: 0.1.3+4 version: 0.1.3+5
environment: environment:
sdk: '>=3.0.3 <4.0.0' sdk: '>=3.0.3 <4.0.0'
@ -38,6 +38,7 @@ dependencies:
omni_datetime_picker: any omni_datetime_picker: any
json_serializable: ^6.8.0 json_serializable: ^6.8.0
waveform_recorder: ^1.8.0 waveform_recorder: ^1.8.0
vimeo_video_player: ^1.0.3
permission_handler: ^12.0.1 permission_handler: ^12.0.1
firebase_messaging: ^16.1.1 firebase_messaging: ^16.1.1
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
@ -46,6 +47,7 @@ dependencies:
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
flutter_timer_countdown: ^1.0.7 flutter_timer_countdown: ^1.0.7
flutter_carousel_widget: ^3.1.0 flutter_carousel_widget: ^3.1.0
flutter_inappwebview: ^6.2.0-beta.3
flutter_local_notifications: ^20.1.0 flutter_local_notifications: ^20.1.0
internet_connection_checker_plus: ^2.9.1+2 internet_connection_checker_plus: ^2.9.1+2

View File

@ -8,40 +8,42 @@ import 'dart:ui' as _i10;
import 'package:audioplayers/audioplayers.dart' as _i4; import 'package:audioplayers/audioplayers.dart' as _i4;
import 'package:dio/dio.dart' as _i2; import 'package:dio/dio.dart' as _i2;
import 'package:firebase_messaging/firebase_messaging.dart' as _i32; import 'package:firebase_messaging/firebase_messaging.dart' as _i34;
import 'package:flutter/material.dart' as _i8; import 'package:flutter/material.dart' as _i8;
import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/mockito.dart' as _i1;
import 'package:mockito/src/dummies.dart' as _i7; import 'package:mockito/src/dummies.dart' as _i7;
import 'package:permission_handler/permission_handler.dart' as _i27; import 'package:permission_handler/permission_handler.dart' as _i29;
import 'package:stacked_services/stacked_services.dart' as _i6; import 'package:stacked_services/stacked_services.dart' as _i6;
import 'package:waveform_recorder/waveform_recorder.dart' as _i5; import 'package:waveform_recorder/waveform_recorder.dart' as _i5;
import 'package:yimaru_app/models/category.dart' as _i15; import 'package:yimaru_app/models/category.dart' as _i15;
import 'package:yimaru_app/models/course.dart' as _i21; import 'package:yimaru_app/models/course.dart' as _i21;
import 'package:yimaru_app/models/course_detail.dart' as _i35; 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_lesson.dart' as _i18;
import 'package:yimaru_app/models/course_progress.dart' as _i17; 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/level.dart' as _i22;
import 'package:yimaru_app/models/module.dart' as _i23; import 'package:yimaru_app/models/module.dart' as _i23;
import 'package:yimaru_app/models/practice.dart' as _i19; import 'package:yimaru_app/models/practice.dart' as _i19;
import 'package:yimaru_app/models/practice_question.dart' as _i20; import 'package:yimaru_app/models/practice_question.dart' as _i20;
import 'package:yimaru_app/models/question.dart' as _i14; import 'package:yimaru_app/models/question.dart' as _i14;
import 'package:yimaru_app/models/subcategory.dart' as _i16; import 'package:yimaru_app/models/subcategory.dart' as _i16;
import 'package:yimaru_app/models/submodule.dart' as _i24;
import 'package:yimaru_app/models/user.dart' as _i12; import 'package:yimaru_app/models/user.dart' as _i12;
import 'package:yimaru_app/services/api_service.dart' as _i13; import 'package:yimaru_app/services/api_service.dart' as _i13;
import 'package:yimaru_app/services/audio_player_service.dart' as _i36; import 'package:yimaru_app/services/audio_player_service.dart' as _i38;
import 'package:yimaru_app/services/authentication_service.dart' as _i11; import 'package:yimaru_app/services/authentication_service.dart' as _i11;
import 'package:yimaru_app/services/course_service.dart' as _i34; import 'package:yimaru_app/services/course_service.dart' as _i36;
import 'package:yimaru_app/services/dio_service.dart' as _i24; import 'package:yimaru_app/services/dio_service.dart' as _i26;
import 'package:yimaru_app/services/google_auth_service.dart' as _i29; import 'package:yimaru_app/services/google_auth_service.dart' as _i31;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i30; import 'package:yimaru_app/services/image_downloader_service.dart' as _i32;
import 'package:yimaru_app/services/image_picker_service.dart' as _i28; import 'package:yimaru_app/services/image_picker_service.dart' as _i30;
import 'package:yimaru_app/services/notification_service.dart' as _i31; import 'package:yimaru_app/services/notification_service.dart' as _i33;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i26; import 'package:yimaru_app/services/permission_handler_service.dart' as _i28;
import 'package:yimaru_app/services/secure_storage_service.dart' as _i3; import 'package:yimaru_app/services/secure_storage_service.dart' as _i3;
import 'package:yimaru_app/services/smart_auth_service.dart' as _i33; import 'package:yimaru_app/services/smart_auth_service.dart' as _i35;
import 'package:yimaru_app/services/status_checker_service.dart' as _i25; import 'package:yimaru_app/services/status_checker_service.dart' as _i27;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i37; import 'package:yimaru_app/services/voice_recorder_service.dart' as _i39;
import 'package:yimaru_app/ui/common/enmus.dart' as _i38; import 'package:yimaru_app/ui/common/enmus.dart' as _i40;
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values // ignore_for_file: avoid_redundant_argument_values
@ -1128,7 +1130,7 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
@override @override
_i9.Future<List<_i15.Category>> getCategories() => (super.noSuchMethod( _i9.Future<List<_i15.Category>> getCategories() => (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCourseCategories, #getCategories,
[], [],
), ),
returnValue: _i9.Future<List<_i15.Category>>.value(<_i15.Category>[]), returnValue: _i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
@ -1140,7 +1142,7 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
_i9.Future<List<_i16.Subcategory>> getSubcategories(int? id) => _i9.Future<List<_i16.Subcategory>> getSubcategories(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCourseSubcategories, #getSubcategories,
[id], [id],
), ),
returnValue: returnValue:
@ -1270,6 +1272,29 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i23.Module>>.value(<_i23.Module>[]), _i9.Future<List<_i23.Module>>.value(<_i23.Module>[]),
) as _i9.Future<List<_i23.Module>>); ) as _i9.Future<List<_i23.Module>>);
@override
_i9.Future<List<_i24.Submodule>> getSubmodules(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubmodules,
[id],
),
returnValue: _i9.Future<List<_i24.Submodule>>.value(<_i24.Submodule>[]),
returnValueForMissingStub:
_i9.Future<List<_i24.Submodule>>.value(<_i24.Submodule>[]),
) as _i9.Future<List<_i24.Submodule>>);
@override
_i9.Future<List<_i25.Lesson>> getLessons(int? id) => (super.noSuchMethod(
Invocation.method(
#getLessons,
[id],
),
returnValue: _i9.Future<List<_i25.Lesson>>.value(<_i25.Lesson>[]),
returnValueForMissingStub:
_i9.Future<List<_i25.Lesson>>.value(<_i25.Lesson>[]),
) as _i9.Future<List<_i25.Lesson>>);
} }
/// A class which mocks [SecureStorageService]. /// A class which mocks [SecureStorageService].
@ -1372,7 +1397,7 @@ class MockSecureStorageService extends _i1.Mock
/// A class which mocks [DioService]. /// A class which mocks [DioService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockDioService extends _i1.Mock implements _i24.DioService { class MockDioService extends _i1.Mock implements _i26.DioService {
@override @override
_i2.Dio get dio => (super.noSuchMethod( _i2.Dio get dio => (super.noSuchMethod(
Invocation.getter(#dio), Invocation.getter(#dio),
@ -1391,7 +1416,7 @@ class MockDioService extends _i1.Mock implements _i24.DioService {
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockStatusCheckerService extends _i1.Mock class MockStatusCheckerService extends _i1.Mock
implements _i25.StatusCheckerService { implements _i27.StatusCheckerService {
@override @override
_i3.SecureStorageService get storage => (super.noSuchMethod( _i3.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage), Invocation.getter(#storage),
@ -1457,40 +1482,40 @@ class MockStatusCheckerService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockPermissionHandlerService extends _i1.Mock class MockPermissionHandlerService extends _i1.Mock
implements _i26.PermissionHandlerService { implements _i28.PermissionHandlerService {
@override @override
_i9.Future<_i27.PermissionStatus> requestPermission( _i9.Future<_i29.PermissionStatus> requestPermission(
_i27.Permission? requestedPermission) => _i29.Permission? requestedPermission) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#requestPermission, #requestPermission,
[requestedPermission], [requestedPermission],
), ),
returnValue: _i9.Future<_i27.PermissionStatus>.value( returnValue: _i9.Future<_i29.PermissionStatus>.value(
_i27.PermissionStatus.denied), _i29.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value( returnValueForMissingStub: _i9.Future<_i29.PermissionStatus>.value(
_i27.PermissionStatus.denied), _i29.PermissionStatus.denied),
) as _i9.Future<_i27.PermissionStatus>); ) as _i9.Future<_i29.PermissionStatus>);
@override @override
_i9.Future<_i27.PermissionStatus> request(_i27.Permission? permission) => _i9.Future<_i29.PermissionStatus> request(_i29.Permission? permission) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#request, #request,
[permission], [permission],
), ),
returnValue: _i9.Future<_i27.PermissionStatus>.value( returnValue: _i9.Future<_i29.PermissionStatus>.value(
_i27.PermissionStatus.denied), _i29.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value( returnValueForMissingStub: _i9.Future<_i29.PermissionStatus>.value(
_i27.PermissionStatus.denied), _i29.PermissionStatus.denied),
) as _i9.Future<_i27.PermissionStatus>); ) as _i9.Future<_i29.PermissionStatus>);
} }
/// A class which mocks [ImagePickerService]. /// A class which mocks [ImagePickerService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockImagePickerService extends _i1.Mock class MockImagePickerService extends _i1.Mock
implements _i28.ImagePickerService { implements _i30.ImagePickerService {
@override @override
_i9.Future<String?> gallery() => (super.noSuchMethod( _i9.Future<String?> gallery() => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1515,7 +1540,7 @@ class MockImagePickerService extends _i1.Mock
/// A class which mocks [GoogleAuthService]. /// A class which mocks [GoogleAuthService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockGoogleAuthService extends _i1.Mock implements _i29.GoogleAuthService { class MockGoogleAuthService extends _i1.Mock implements _i31.GoogleAuthService {
@override @override
int get listenersCount => (super.noSuchMethod( int get listenersCount => (super.noSuchMethod(
Invocation.getter(#listenersCount), Invocation.getter(#listenersCount),
@ -1585,7 +1610,7 @@ class MockGoogleAuthService extends _i1.Mock implements _i29.GoogleAuthService {
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockImageDownloaderService extends _i1.Mock class MockImageDownloaderService extends _i1.Mock
implements _i30.ImageDownloaderService { implements _i32.ImageDownloaderService {
@override @override
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod( _i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1614,7 +1639,7 @@ class MockImageDownloaderService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockNotificationService extends _i1.Mock class MockNotificationService extends _i1.Mock
implements _i31.NotificationService { implements _i33.NotificationService {
@override @override
_i9.Future<void> initialize() => (super.noSuchMethod( _i9.Future<void> initialize() => (super.noSuchMethod(
Invocation.method( Invocation.method(
@ -1636,7 +1661,7 @@ class MockNotificationService extends _i1.Mock
) as _i9.Future<void>); ) as _i9.Future<void>);
@override @override
_i9.Future<void> showNotification(_i32.RemoteMessage? message) => _i9.Future<void> showNotification(_i34.RemoteMessage? message) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#showNotification, #showNotification,
@ -1670,7 +1695,7 @@ class MockNotificationService extends _i1.Mock
/// A class which mocks [SmartAuthService]. /// A class which mocks [SmartAuthService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockSmartAuthService extends _i1.Mock implements _i33.SmartAuthService { class MockSmartAuthService extends _i1.Mock implements _i35.SmartAuthService {
@override @override
bool get listenForMultipleSms => (super.noSuchMethod( bool get listenForMultipleSms => (super.noSuchMethod(
Invocation.getter(#listenForMultipleSms), Invocation.getter(#listenForMultipleSms),
@ -1702,26 +1727,26 @@ class MockSmartAuthService extends _i1.Mock implements _i33.SmartAuthService {
/// A class which mocks [CourseService]. /// A class which mocks [CourseService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockCourseService extends _i1.Mock implements _i34.CourseService { class MockCourseService extends _i1.Mock implements _i36.CourseService {
@override @override
_i9.Future<List<_i35.CourseDetail>> getCoursesDetail(int? id) => _i9.Future<List<_i37.CourseDetail>> getCoursesDetail(int? id) =>
(super.noSuchMethod( (super.noSuchMethod(
Invocation.method( Invocation.method(
#getCoursesDetail, #getCoursesDetail,
[id], [id],
), ),
returnValue: returnValue:
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]), _i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
returnValueForMissingStub: returnValueForMissingStub:
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]), _i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
) as _i9.Future<List<_i35.CourseDetail>>); ) as _i9.Future<List<_i37.CourseDetail>>);
} }
/// A class which mocks [AudioPlayerService]. /// A class which mocks [AudioPlayerService].
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockAudioPlayerService extends _i1.Mock class MockAudioPlayerService extends _i1.Mock
implements _i36.AudioPlayerService { implements _i38.AudioPlayerService {
@override @override
_i4.AudioPlayer get player => (super.noSuchMethod( _i4.AudioPlayer get player => (super.noSuchMethod(
Invocation.getter(#player), Invocation.getter(#player),
@ -1845,13 +1870,13 @@ class MockAudioPlayerService extends _i1.Mock
/// ///
/// See the documentation for Mockito's code generation for more information. /// See the documentation for Mockito's code generation for more information.
class MockVoiceRecorderService extends _i1.Mock class MockVoiceRecorderService extends _i1.Mock
implements _i37.VoiceRecorderService { implements _i39.VoiceRecorderService {
@override @override
_i38.VoiceRecordingState get recordingState => (super.noSuchMethod( _i40.VoiceRecordingState get recordingState => (super.noSuchMethod(
Invocation.getter(#recordingState), Invocation.getter(#recordingState),
returnValue: _i38.VoiceRecordingState.pending, returnValue: _i40.VoiceRecordingState.pending,
returnValueForMissingStub: _i38.VoiceRecordingState.pending, returnValueForMissingStub: _i40.VoiceRecordingState.pending,
) as _i38.VoiceRecordingState); ) as _i40.VoiceRecordingState);
@override @override
_i5.WaveformRecorderController get waveController => (super.noSuchMethod( _i5.WaveformRecorderController get waveController => (super.noSuchMethod(

View File

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

View File

@ -11,6 +11,7 @@
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <firebase_core/firebase_core_plugin_c_api.h> #include <firebase_core/firebase_core_plugin_c_api.h>
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h> #include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h> #include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <record_windows/record_windows_plugin_c_api.h> #include <record_windows/record_windows_plugin_c_api.h>
@ -26,6 +27,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
FirebaseCorePluginCApiRegisterWithRegistrar( FirebaseCorePluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar( FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar( PermissionHandlerWindowsPluginRegisterWithRegistrar(

View File

@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus connectivity_plus
file_selector_windows file_selector_windows
firebase_core firebase_core
flutter_inappwebview_windows
flutter_secure_storage_windows flutter_secure_storage_windows
permission_handler_windows permission_handler_windows
record_windows record_windows