Compare commits

..

No commits in common. "ee981ceba9150d39da7a8023dfd8d5c5c21bc497" and "76ce3853556845292b3f64eddf5a5e531e5cb269" have entirely different histories.

36 changed files with 546 additions and 1233 deletions

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,52 +0,0 @@
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);
}

View File

@ -1,35 +0,0 @@
// 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,
};

View File

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

@ -1,31 +0,0 @@
// 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,9 +13,7 @@ import 'package:yimaru_app/services/dio_service.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import '../app/app.locator.dart';
import '../models/lesson.dart';
import '../models/module.dart';
import '../models/submodule.dart';
import '../ui/common/enmus.dart';
class ApiService {
@ -676,52 +674,4 @@ class ApiService {
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,8 +10,6 @@ String kCoursesUrl = 'courses';
String kModulesUrl = 'modules';
String kLessonsUrl = 'lessons';
String kRegisterUrl = 'register';
String kCategoryUrl = 'categories';
@ -26,8 +24,6 @@ String kResendOtpUrl = 'resend-otp';
String kGetUserUrl = 'user-profile';
String kSubmodulesUrl = 'sub-modules';
String kSubcoursesUrl = 'sub-courses';
String kCompleteLessonUrl = 'complete';

View File

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

View File

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

View File

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

View File

@ -1,30 +1,31 @@
import 'package:flutter/material.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/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';
import '../../widgets/custom_elevated_button.dart';
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);
@override
void onViewModelReady(LearnLessonViewModel viewModel) async {
await viewModel.getLessons(submodule.id ?? 0);
super.onViewModelReady(viewModel);
}
final String title;
final String topics;
final String subtitle;
final String description;
final List<Map<String, dynamic>> practices;
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) {
double half = screenHeight(context) / 2;
@ -116,63 +117,95 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
verticalSpaceTiny,
_buildSubtitle(),
verticalSpaceSmall,
_buildTopics(),
verticalSpaceSmall,
_buildModuleProgress(),
verticalSpaceMedium,
verticalSpaceMedium,
_buildMotivationCard(),
verticalSpaceMedium,
_buildHeader(),
verticalSpaceMedium,
_buildListViewBuilder(viewModel),
// _buildModuleProgress(),
// verticalSpaceMedium,
// _buildContinueButton(),
// verticalSpaceMedium,
// _buildMotivationCard(),
// verticalSpaceMedium,
//_buildHeader(),
//verticalSpaceMedium,
// _buildListView(viewModel),
getPadding(context),
_buildStartButton(viewModel),
verticalSpaceSmall,
_buildPracticeButton(viewModel)
];
Widget _buildTitle() => Text(
submodule.title ?? '',
title,
style: style16DG600,
);
Widget _buildSubtitle() => Text(
submodule.description ?? '',
subtitle,
style: style14DG600,
);
Widget _buildTopics() => Text(
topics,
style: style14DG500,
);
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 _buildHeader() => Text(
'Lessons in this module',
title,
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(
shrinkWrap: true,
itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
lesson: viewModel.lessons[index],
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(viewModel.lessons[index]),
title: viewModel.lessons[index]['title'],
status: viewModel.lessons[index]['status'],
thumbnail: viewModel.lessons[index]['thumbnail'],
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
title: title, practices: practices, description: description),
// onPracticeTap: () async => await viewModel.navigateToLearnPractice(),
),
);
Widget _buildTile({
required Lesson lesson,
required GestureTapCallback? onLessonTap,
required String title,
required String thumbnail,
GestureTapCallback? onLessonTap,
required ProgressStatuses status,
GestureTapCallback? onPracticeTap,
}) =>
LearnLessonTile(
lesson: lesson,
title: title,
status: status,
thumbnail: thumbnail,
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -4,42 +4,47 @@ import 'package:yimaru_app/app/app.router.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';
class LearnLessonViewModel extends BaseViewModel {
// Dependency injection
final _apiService = locator<ApiService>();
final _statusChecker = locator<StatusCheckerService>();
final _navigationService = locator<NavigationService>();
// Learn lessons
List<Lesson> _lessons = [];
// Lessons
final List<Map<String, dynamic>> _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<Lesson> get lessons => _lessons;
List<Map<String, dynamic>> get lessons => _lessons;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnLessonDetail(Lesson lesson) async =>
await _navigationService.navigateToLearnLessonDetailView(lesson: lesson);
Future<void> navigateToLearnLessonDetail(
{required String title,
required List<Map<String, dynamic>> practices,
required String description}) async =>
await _navigationService.navigateToLearnLessonDetailView(
title: title, practices: practices, description: description);
// Remote api call
// Learn modules
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));
}
}
}
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!',
);
}

View File

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

View File

@ -1,10 +1,11 @@
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/app_constants.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../../../app/app.locator.dart';
import '../../../services/status_checker_service.dart';
@ -19,10 +20,6 @@ class LearnLessonDetailViewModel extends BaseViewModel {
ChewieController? get chewieController => _chewieController;
InAppWebViewController? _webViewController;
InAppWebViewController? get webViewController => _webViewController;
VideoPlayerController? _videoPlayerController;
VideoPlayerController? get videoPlayerController => _videoPlayerController;
@ -37,22 +34,32 @@ class LearnLessonDetailViewModel extends BaseViewModel {
await _chewieController?.pause();
}
void initializePlayer(InAppWebViewController controller){
_webViewController = controller;
rebuildUi();
Future<void> initializePlayer() async =>
await runBusyFuture(_initializePlayer(),
busyObject: StateObjects.loadLessonVideo);
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);
}
void onLoadVideoStart() {
setBusyForObject(StateObjects.loadLessonVideo, true);
rebuildUi();
// rebuildUi();
}
void onLoadVideoComplete() {
setBusyForObject(StateObjects.loadLessonVideo, false);
rebuildUi();
}
// Navigation
void pop() => _navigationService.back();

View File

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

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.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/overall_learn_progress.dart';
import '../../../models/module.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_module_viewmodel.dart';
@ -58,7 +57,6 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
);
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
title: 'Modules',
onTap: viewModel.pop,
showBackButton: true,
);
@ -81,10 +79,10 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
verticalSpaceLarge,
verticalSpaceMedium,
_buildOverallProgress(),
verticalSpaceMedium,
_buildListViewBuilder(viewModel)
_buildListView(viewModel)
];
Widget _buildTitle() => Text(
@ -97,18 +95,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
style: style14DG400,
);
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 _buildOverallProgress() => const OverallLearnProgress();
Widget _buildListView(LearnModuleViewModel viewModel) => ListView.builder(
shrinkWrap: true,
@ -116,17 +103,18 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index],
onModuleTap: () async => await viewModel
.navigateToLearnSubmodule(viewModel.modules[index]),
),
onLessonTap: () {},
onPracticeTap: () {}),
);
Widget _buildTile({
required Module module,
required GestureTapCallback onModuleTap,
required GestureTapCallback onLessonTap,
required GestureTapCallback onPracticeTap,
}) =>
LearnModuleTile(
module: module,
onModuleTap: onModuleTap,
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
);
}

View File

@ -24,8 +24,27 @@ class LearnModuleViewModel extends BaseViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnSubmodule(Module module) async =>
await _navigationService.navigateToLearnSubmoduleView(module: module);
Future<void> navigateToLearnLesson(
{required String title,
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

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

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

View File

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

View File

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

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

View File

@ -4,8 +4,7 @@ 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});
const OverallLearnProgress({super.key});
@override
Widget build(BuildContext context) => _buildContainer();
@ -13,8 +12,8 @@ class OverallLearnProgress extends StatelessWidget {
Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4),
color: kcPrimaryColor.withOpacity(0.1),
),
child: _buildProgressSection(),
);

View File

@ -8,7 +8,6 @@
#include <audioplayers_linux/audioplayers_linux_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 <record_linux/record_linux_plugin.h>
@ -19,9 +18,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
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 =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);

View File

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

View File

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

View File

@ -558,78 +558,6 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct dev"
description:
@ -1813,14 +1741,6 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

View File

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

View File

@ -8,42 +8,40 @@ 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 _i32;
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 _i27;
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_detail.dart' as _i35;
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/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/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 _i36;
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 _i34;
import 'package:yimaru_app/services/dio_service.dart' as _i24;
import 'package:yimaru_app/services/google_auth_service.dart' as _i29;
import 'package:yimaru_app/services/image_downloader_service.dart' as _i30;
import 'package:yimaru_app/services/image_picker_service.dart' as _i28;
import 'package:yimaru_app/services/notification_service.dart' as _i31;
import 'package:yimaru_app/services/permission_handler_service.dart' as _i26;
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 _i33;
import 'package:yimaru_app/services/status_checker_service.dart' as _i25;
import 'package:yimaru_app/services/voice_recorder_service.dart' as _i37;
import 'package:yimaru_app/ui/common/enmus.dart' as _i38;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@ -1130,7 +1128,7 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
@override
_i9.Future<List<_i15.Category>> getCategories() => (super.noSuchMethod(
Invocation.method(
#getCategories,
#getCourseCategories,
[],
),
returnValue: _i9.Future<List<_i15.Category>>.value(<_i15.Category>[]),
@ -1142,7 +1140,7 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
_i9.Future<List<_i16.Subcategory>> getSubcategories(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getSubcategories,
#getCourseSubcategories,
[id],
),
returnValue:
@ -1272,29 +1270,6 @@ class MockApiService extends _i1.Mock implements _i13.ApiService {
returnValueForMissingStub:
_i9.Future<List<_i23.Module>>.value(<_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].
@ -1397,7 +1372,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 _i24.DioService {
@override
_i2.Dio get dio => (super.noSuchMethod(
Invocation.getter(#dio),
@ -1416,7 +1391,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 _i25.StatusCheckerService {
@override
_i3.SecureStorageService get storage => (super.noSuchMethod(
Invocation.getter(#storage),
@ -1482,40 +1457,40 @@ class MockStatusCheckerService extends _i1.Mock
///
/// See the documentation for Mockito's code generation for more information.
class MockPermissionHandlerService extends _i1.Mock
implements _i28.PermissionHandlerService {
implements _i26.PermissionHandlerService {
@override
_i9.Future<_i29.PermissionStatus> requestPermission(
_i29.Permission? requestedPermission) =>
_i9.Future<_i27.PermissionStatus> requestPermission(
_i27.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<_i27.PermissionStatus>.value(
_i27.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value(
_i27.PermissionStatus.denied),
) as _i9.Future<_i27.PermissionStatus>);
@override
_i9.Future<_i29.PermissionStatus> request(_i29.Permission? permission) =>
_i9.Future<_i27.PermissionStatus> request(_i27.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<_i27.PermissionStatus>.value(
_i27.PermissionStatus.denied),
returnValueForMissingStub: _i9.Future<_i27.PermissionStatus>.value(
_i27.PermissionStatus.denied),
) as _i9.Future<_i27.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 _i28.ImagePickerService {
@override
_i9.Future<String?> gallery() => (super.noSuchMethod(
Invocation.method(
@ -1540,7 +1515,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 _i29.GoogleAuthService {
@override
int get listenersCount => (super.noSuchMethod(
Invocation.getter(#listenersCount),
@ -1610,7 +1585,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 _i30.ImageDownloaderService {
@override
_i9.Future<String> downloader(String? networkImage) => (super.noSuchMethod(
Invocation.method(
@ -1639,7 +1614,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 _i31.NotificationService {
@override
_i9.Future<void> initialize() => (super.noSuchMethod(
Invocation.method(
@ -1661,7 +1636,7 @@ class MockNotificationService extends _i1.Mock
) as _i9.Future<void>);
@override
_i9.Future<void> showNotification(_i34.RemoteMessage? message) =>
_i9.Future<void> showNotification(_i32.RemoteMessage? message) =>
(super.noSuchMethod(
Invocation.method(
#showNotification,
@ -1695,7 +1670,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 _i33.SmartAuthService {
@override
bool get listenForMultipleSms => (super.noSuchMethod(
Invocation.getter(#listenForMultipleSms),
@ -1727,26 +1702,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 _i34.CourseService {
@override
_i9.Future<List<_i37.CourseDetail>> getCoursesDetail(int? id) =>
_i9.Future<List<_i35.CourseDetail>> getCoursesDetail(int? id) =>
(super.noSuchMethod(
Invocation.method(
#getCoursesDetail,
[id],
),
returnValue:
_i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]),
returnValueForMissingStub:
_i9.Future<List<_i37.CourseDetail>>.value(<_i37.CourseDetail>[]),
) as _i9.Future<List<_i37.CourseDetail>>);
_i9.Future<List<_i35.CourseDetail>>.value(<_i35.CourseDetail>[]),
) as _i9.Future<List<_i35.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 _i36.AudioPlayerService {
@override
_i4.AudioPlayer get player => (super.noSuchMethod(
Invocation.getter(#player),
@ -1870,13 +1845,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 _i37.VoiceRecorderService {
@override
_i40.VoiceRecordingState get recordingState => (super.noSuchMethod(
_i38.VoiceRecordingState get recordingState => (super.noSuchMethod(
Invocation.getter(#recordingState),
returnValue: _i40.VoiceRecordingState.pending,
returnValueForMissingStub: _i40.VoiceRecordingState.pending,
) as _i40.VoiceRecordingState);
returnValue: _i38.VoiceRecordingState.pending,
returnValueForMissingStub: _i38.VoiceRecordingState.pending,
) as _i38.VoiceRecordingState);
@override
_i5.WaveformRecorderController get waveController => (super.noSuchMethod(

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('LearnSubmoduleViewModel Tests -', () {
setUp(() => registerServices());
tearDown(() => locator.reset());
});
}

View File

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

View File

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