diff --git a/android/app/google-services.json b/android/app/google-services.json index 5edf582..56a8b0b 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -14,11 +14,11 @@ }, "oauth_client": [ { - "client_id": "574860813475-glgnkruic7dflaomb59el8994b7hhfga.apps.googleusercontent.com", + "client_id": "574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com", "client_type": 1, "android_info": { "package_name": "com.yimaru.lms.app", - "certificate_hash": "378836a3aa9f36958b6c6c69bc67e3195352f68d" + "certificate_hash": "928ead08b5e39d6a861a55ae7cceb8c402d1ee7a" } }, { diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html index 9a79f4e..1e24d19 100644 --- a/android/build/reports/problems/problems-report.html +++ b/android/build/reports/problems/problems-report.html @@ -646,7 +646,7 @@ code + .copy-button { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 6388f31..a20f2c4 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 37ff765..dbbc5c6 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,9 +19,10 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "9.0.1" apply false + id("com.android.application") version "9.1.0" apply false id("org.jetbrains.kotlin.android") version "2.3.0" apply false id("com.google.gms.google-services") version("4.4.4") apply false + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } diff --git a/lib/app/app.dart b/lib/app/app.dart index eab8a42..2c6972c 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -48,6 +48,8 @@ import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/course_service.dart'; import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart'; import 'package:yimaru_app/ui/views/course/course_view.dart'; +import 'package:yimaru_app/services/audio_player_service.dart'; +import 'package:yimaru_app/services/voice_recorder_service.dart'; // @stacked-import @StackedApp( @@ -104,6 +106,8 @@ import 'package:yimaru_app/ui/views/course/course_view.dart'; LazySingleton(classType: NotificationService), LazySingleton(classType: SmartAuthService), LazySingleton(classType: CourseService), + LazySingleton(classType: AudioPlayerService), + LazySingleton(classType: VoiceRecorderService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index 569deb2..2e05699 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -12,6 +12,7 @@ import 'package:stacked_services/src/navigation/navigation_service.dart'; import 'package:stacked_shared/stacked_shared.dart'; import '../services/api_service.dart'; +import '../services/audio_player_service.dart'; import '../services/authentication_service.dart'; import '../services/course_service.dart'; import '../services/dio_service.dart'; @@ -23,6 +24,7 @@ import '../services/permission_handler_service.dart'; import '../services/secure_storage_service.dart'; import '../services/smart_auth_service.dart'; import '../services/status_checker_service.dart'; +import '../services/voice_recorder_service.dart'; final locator = StackedLocator.instance; @@ -50,4 +52,6 @@ Future setupLocator({ locator.registerLazySingleton(() => NotificationService()); locator.registerLazySingleton(() => SmartAuthService()); locator.registerLazySingleton(() => CourseService()); + locator.registerLazySingleton(() => AudioPlayerService()); + locator.registerLazySingleton(() => VoiceRecorderService()); } diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index d820fb7..3d47d9f 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -445,8 +445,15 @@ class StackedRouter extends _i1.RouterBase { ); }, _i23.LearnLessonView: (data) { + final args = data.getArgs(nullOk: false); return _i36.MaterialPageRoute( - builder: (context) => const _i23.LearnLessonView(), + builder: (context) => _i23.LearnLessonView( + key: args.key, + title: args.title, + topics: args.topics, + subtitle: args.subtitle, + practices: args.practices, + description: args.description), settings: data, ); }, @@ -457,8 +464,13 @@ class StackedRouter extends _i1.RouterBase { ); }, _i25.LearnLessonDetailView: (data) { + final args = data.getArgs(nullOk: false); return _i36.MaterialPageRoute( - builder: (context) => const _i25.LearnLessonDetailView(), + builder: (context) => _i25.LearnLessonDetailView( + key: args.key, + title: args.title, + practices: args.practices, + description: args.description), settings: data, ); }, @@ -469,6 +481,7 @@ class StackedRouter extends _i1.RouterBase { key: args.key, title: args.title, subtitle: args.subtitle, + practices: args.practices, buttonLabel: args.buttonLabel), settings: data, ); @@ -604,11 +617,100 @@ class AssessmentViewArguments { } } +class LearnLessonViewArguments { + const LearnLessonViewArguments({ + this.key, + required this.title, + required this.topics, + required this.subtitle, + required this.practices, + required this.description, + }); + + final _i36.Key? key; + + final String title; + + final String topics; + + final String subtitle; + + final List> practices; + + final String description; + + @override + String toString() { + return '{"key": "$key", "title": "$title", "topics": "$topics", "subtitle": "$subtitle", "practices": "$practices", "description": "$description"}'; + } + + @override + bool operator ==(covariant LearnLessonViewArguments other) { + if (identical(this, other)) return true; + return other.key == key && + other.title == title && + other.topics == topics && + other.subtitle == subtitle && + other.practices == practices && + other.description == description; + } + + @override + int get hashCode { + return key.hashCode ^ + title.hashCode ^ + topics.hashCode ^ + subtitle.hashCode ^ + practices.hashCode ^ + description.hashCode; + } +} + +class LearnLessonDetailViewArguments { + const LearnLessonDetailViewArguments({ + this.key, + required this.title, + required this.practices, + required this.description, + }); + + final _i36.Key? key; + + final String title; + + final List> practices; + + final String description; + + @override + String toString() { + return '{"key": "$key", "title": "$title", "practices": "$practices", "description": "$description"}'; + } + + @override + bool operator ==(covariant LearnLessonDetailViewArguments other) { + if (identical(this, other)) return true; + return other.key == key && + other.title == title && + other.practices == practices && + other.description == description; + } + + @override + int get hashCode { + return key.hashCode ^ + title.hashCode ^ + practices.hashCode ^ + description.hashCode; + } +} + class LearnPracticeViewArguments { const LearnPracticeViewArguments({ this.key, required this.title, required this.subtitle, + required this.practices, required this.buttonLabel, }); @@ -618,11 +720,13 @@ class LearnPracticeViewArguments { final String subtitle; + final List> practices; + final String buttonLabel; @override String toString() { - return '{"key": "$key", "title": "$title", "subtitle": "$subtitle", "buttonLabel": "$buttonLabel"}'; + return '{"key": "$key", "title": "$title", "subtitle": "$subtitle", "practices": "$practices", "buttonLabel": "$buttonLabel"}'; } @override @@ -631,6 +735,7 @@ class LearnPracticeViewArguments { return other.key == key && other.title == title && other.subtitle == subtitle && + other.practices == practices && other.buttonLabel == buttonLabel; } @@ -639,6 +744,7 @@ class LearnPracticeViewArguments { return key.hashCode ^ title.hashCode ^ subtitle.hashCode ^ + practices.hashCode ^ buttonLabel.hashCode; } } @@ -1133,14 +1239,27 @@ extension NavigatorStateExtension on _i41.NavigationService { transition: transition); } - Future navigateToLearnLessonView([ + Future navigateToLearnLessonView({ + _i36.Key? key, + required String title, + required String topics, + required String subtitle, + required List> practices, + required String description, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - ]) async { + }) async { return navigateTo(Routes.learnLessonView, + arguments: LearnLessonViewArguments( + key: key, + title: title, + topics: topics, + subtitle: subtitle, + practices: practices, + description: description), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1161,14 +1280,23 @@ extension NavigatorStateExtension on _i41.NavigationService { transition: transition); } - Future navigateToLearnLessonDetailView([ + Future navigateToLearnLessonDetailView({ + _i36.Key? key, + required String title, + required List> practices, + required String description, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - ]) async { + }) async { return navigateTo(Routes.learnLessonDetailView, + arguments: LearnLessonDetailViewArguments( + key: key, + title: title, + practices: practices, + description: description), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1179,6 +1307,7 @@ extension NavigatorStateExtension on _i41.NavigationService { _i36.Key? key, required String title, required String subtitle, + required List> practices, required String buttonLabel, int? routerId, bool preventDuplicates = true, @@ -1191,6 +1320,7 @@ extension NavigatorStateExtension on _i41.NavigationService { key: key, title: title, subtitle: subtitle, + practices: practices, buttonLabel: buttonLabel), id: routerId, preventDuplicates: preventDuplicates, @@ -1645,14 +1775,27 @@ extension NavigatorStateExtension on _i41.NavigationService { transition: transition); } - Future replaceWithLearnLessonView([ + Future replaceWithLearnLessonView({ + _i36.Key? key, + required String title, + required String topics, + required String subtitle, + required List> practices, + required String description, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - ]) async { + }) async { return replaceWith(Routes.learnLessonView, + arguments: LearnLessonViewArguments( + key: key, + title: title, + topics: topics, + subtitle: subtitle, + practices: practices, + description: description), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1673,14 +1816,23 @@ extension NavigatorStateExtension on _i41.NavigationService { transition: transition); } - Future replaceWithLearnLessonDetailView([ + Future replaceWithLearnLessonDetailView({ + _i36.Key? key, + required String title, + required List> practices, + required String description, int? routerId, bool preventDuplicates = true, Map? parameters, Widget Function(BuildContext, Animation, Animation, Widget)? transition, - ]) async { + }) async { return replaceWith(Routes.learnLessonDetailView, + arguments: LearnLessonDetailViewArguments( + key: key, + title: title, + practices: practices, + description: description), id: routerId, preventDuplicates: preventDuplicates, parameters: parameters, @@ -1691,6 +1843,7 @@ extension NavigatorStateExtension on _i41.NavigationService { _i36.Key? key, required String title, required String subtitle, + required List> practices, required String buttonLabel, int? routerId, bool preventDuplicates = true, @@ -1703,6 +1856,7 @@ extension NavigatorStateExtension on _i41.NavigationService { key: key, title: title, subtitle: subtitle, + practices: practices, buttonLabel: buttonLabel), id: routerId, preventDuplicates: preventDuplicates, diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 76ed459..27ce554 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -64,7 +64,7 @@ class DefaultFirebaseOptions { projectId: 'yimaru-lms-e834e', storageBucket: 'yimaru-lms-e834e.firebasestorage.app', androidClientId: - '574860813475-glgnkruic7dflaomb59el8994b7hhfga.apps.googleusercontent.com', + '574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com', iosBundleId: 'com.yimaru.lms.app', ); } diff --git a/lib/models/practice.dart b/lib/models/practice.dart index 913104a..54907d7 100644 --- a/lib/models/practice.dart +++ b/lib/models/practice.dart @@ -25,12 +25,16 @@ class Practice { @JsonKey(name: 'shuffle_questions') final bool? shuffleQuestions; - - - const Practice({ - this.id, - this.title,this.status,this.setType,this.persona,this.ownerId,this.ownerType,this.description,this.shuffleQuestions - }); + const Practice( + {this.id, + this.title, + this.status, + this.setType, + this.persona, + this.ownerId, + this.ownerType, + this.description, + this.shuffleQuestions}); factory Practice.fromJson(Map json) => _$PracticeFromJson(json); diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 88f987f..d92611b 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -512,20 +512,19 @@ class ApiService { } } - // Course practices - Future> getCoursePractices(Map data) async { + Future> getCoursePractices(Map data) async { try { List coursePractices = []; - final Response response = await _service.dio.get( - '$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice',data: data); + final Response response = await _service.dio + .get('$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice', data: data); if (response.statusCode == 200) { var data = response.data; var decodedData = data['data'] as List; coursePractices = decodedData.map( - (e) { + (e) { return Practice.fromJson(e); }, ).toList(); @@ -537,20 +536,19 @@ class ApiService { } } - // Course practic questions Future> getCoursePracticeQuestions(int id) async { try { List coursePracticeQuestions = []; - final Response response = await _service.dio.get( - '$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions'); + final Response response = await _service.dio + .get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions'); if (response.statusCode == 200) { var data = response.data; var decodedData = data['data'] as List; coursePracticeQuestions = decodedData.map( - (e) { + (e) { return PracticeQuestion.fromJson(e); }, ).toList(); diff --git a/lib/services/audio_player_service.dart b/lib/services/audio_player_service.dart new file mode 100644 index 0000000..7fbde1c --- /dev/null +++ b/lib/services/audio_player_service.dart @@ -0,0 +1,41 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:stacked/stacked.dart'; + +import '../ui/common/helper_functions.dart'; + +class AudioPlayerService with ListenableServiceMixin { + final AudioPlayer _player = AudioPlayer(); + + AudioPlayer get player => _player; + + AudioPlayerService() { + _player.setReleaseMode(ReleaseMode.stop); + } + + // Streams + Stream get positionStream => _player.onPositionChanged; + Stream get durationStream => _player.onDurationChanged; + + // Optional: player state + Stream get stateStream => _player.onPlayerStateChanged; + + Future playUrl(String url) async { + final playableUrl = getPlayableUrl(url); + + if (playableUrl == null) { + throw Exception("Invalid audio URL"); + } + + await _player.play(UrlSource(playableUrl)); + } + + Future playLocal(String url) async { + + + await _player.play(UrlSource(url)); + } + + Future pause() async => await _player.pause(); + + Future seek(Duration position) async => await _player.seek(position); +} diff --git a/lib/services/voice_recorder_service.dart b/lib/services/voice_recorder_service.dart new file mode 100644 index 0000000..68664ee --- /dev/null +++ b/lib/services/voice_recorder_service.dart @@ -0,0 +1,35 @@ +import 'package:stacked/stacked.dart'; +import 'package:waveform_recorder/waveform_recorder.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; + +class VoiceRecorderService with ListenableServiceMixin { + VoiceRecordingState _recordingState = VoiceRecordingState.pending; + + VoiceRecordingState get recordingState => _recordingState; + + final WaveformRecorderController _waveController = + WaveformRecorderController(); + + WaveformRecorderController get waveController => _waveController; + + + Future startRecording() async { + + await _waveController.startRecording(); + _recordingState = VoiceRecordingState.recording; + notifyListeners(); + } + + Future stopRecording() async { + await _waveController.stopRecording(); + _recordingState = VoiceRecordingState.pending; + notifyListeners(); + } + + Future getRecordedAudio() async { + final file = _waveController.file; + print('RECORDED $file'); + if (file == null) return null; + return file.path; + } +} diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 96e247b..768cdf1 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -19,7 +19,6 @@ String kSubcoursesUrl = 'sub-courses'; String kCompleteLessonUrl = 'complete'; - String kResetPassword = 'resetPassword'; String kCourseCategoryUrl = 'categories'; diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index f11869c..57f9cf4 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -1,11 +1,14 @@ // Login method enum LoginMethod { phone, email, google } +// Response status +enum ResponseStatus { success, failure } + // Sign-up method enum SignUpMethod { phone, email, google } -// Response status -enum ResponseStatus { success, failure } +// Voice recording state +enum VoiceRecordingState { pending, recording } // Levels enum ProficiencyLevels { a1, a2, b1, b2, none } @@ -18,6 +21,7 @@ enum DuolingoAssessmentType { speaking, reading, writing, listening } // State object enum StateObjects { + none, courses, homeView, register, @@ -37,5 +41,10 @@ enum StateObjects { courseCategories, profileCompletion, registerWithGoogle, + learnPracticeSample, + learnPracticeAnswer, loginWithPhoneNumber, + learnPracticeQuestion, + recordLearnPracticeAnswer, + } diff --git a/lib/ui/common/helper_functions.dart b/lib/ui/common/helper_functions.dart index d96fcfd..3cb9782 100644 --- a/lib/ui/common/helper_functions.dart +++ b/lib/ui/common/helper_functions.dart @@ -43,3 +43,28 @@ Color getColor() { return kcAquamarine.withValues(alpha: 0.2); } } + +String? getPlayableUrl(String url) { + try { + // Case 1: /file/d/FILE_ID/view + final fileIdRegex = RegExp(r'/file/d/([a-zA-Z0-9_-]+)'); + final match1 = fileIdRegex.firstMatch(url); + + if (match1 != null) { + final fileId = match1.group(1); + return "https://drive.google.com/uc?export=download&id=$fileId"; + } + + // Case 2: open?id=FILE_ID + final uri = Uri.parse(url); + if (uri.queryParameters.containsKey('id')) { + final fileId = uri.queryParameters['id']; + return "https://drive.google.com/uc?export=download&id=$fileId"; + } + + // Already converted or normal URL + return url; + } catch (e) { + return null; + } +} diff --git a/lib/ui/views/assessment/assessment_viewmodel.dart b/lib/ui/views/assessment/assessment_viewmodel.dart index dfb0446..79b988e 100644 --- a/lib/ui/views/assessment/assessment_viewmodel.dart +++ b/lib/ui/views/assessment/assessment_viewmodel.dart @@ -237,6 +237,8 @@ class AssessmentViewModel extends BaseViewModel { } // Navigation + void pop() => _navigationService.back(); + Future navigateToLanguage() async => await _navigationService.navigateToLanguageView(); diff --git a/lib/ui/views/assessment/screens/assessment_form_screen.dart b/lib/ui/views/assessment/screens/assessment_form_screen.dart index 1fe60fb..06e5ed2 100644 --- a/lib/ui/views/assessment/screens/assessment_form_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_form_screen.dart @@ -26,6 +26,7 @@ class AssessmentFormScreen extends ViewModelWidget { isLoading: viewModel.isBusy, isEmpty: viewModel.assessments.isEmpty, onTap: () async => await viewModel.getAssessments(), + onPop: viewModel.assessments.isEmpty ? viewModel.pop : null, ); Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) => diff --git a/lib/ui/views/assessment/screens/assessment_loading_screen.dart b/lib/ui/views/assessment/screens/assessment_loading_screen.dart index f5e90f7..1c1503a 100644 --- a/lib/ui/views/assessment/screens/assessment_loading_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_loading_screen.dart @@ -8,9 +8,14 @@ import '../../../widgets/refresh_button.dart'; class AssessmentLoadingScreen extends StatelessWidget { final bool isEmpty; final bool isLoading; + final GestureTapCallback? onPop; final GestureTapCallback? onTap; const AssessmentLoadingScreen( - {super.key, this.onTap, required this.isEmpty, required this.isLoading}); + {super.key, + this.onTap, + this.onPop, + required this.isEmpty, + required this.isLoading}); @override Widget build(BuildContext context) => _buildScaffoldWrapper(); @@ -35,7 +40,8 @@ class AssessmentLoadingScreen extends StatelessWidget { List _buildColumnChildren() => [_buildAppBar(), _buildBody()]; - Widget _buildAppBar() => const LargeAppBar( + Widget _buildAppBar() => LargeAppBar( + onPop: onPop, showBackButton: true, showLanguageSelection: true, ); diff --git a/lib/ui/views/course_category/course_category_viewmodel.dart b/lib/ui/views/course_category/course_category_viewmodel.dart index 6d08e5a..6cc881e 100644 --- a/lib/ui/views/course_category/course_category_viewmodel.dart +++ b/lib/ui/views/course_category/course_category_viewmodel.dart @@ -50,8 +50,7 @@ class CourseCategoryViewModel extends ReactiveViewModel { if (await _statusChecker.checkConnection()) { _categories = await _apiService.getCourseCategories(); - rebuildUi(); - + rebuildUi(); } } } diff --git a/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart b/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart index 6f86852..e53d2a3 100644 --- a/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart +++ b/lib/ui/views/course_lesson_detail/course_lesson_detail_view.dart @@ -146,7 +146,7 @@ class CourseLessonDetailView extends StackedView { ); Widget _buildPracticeButton(CourseLessonDetailViewModel viewModel) => - CustomElevatedButton( + CustomElevatedButton( height: 55, text: 'Practice', borderRadius: 12, diff --git a/lib/ui/views/course_practice/course_practice_viewmodel.dart b/lib/ui/views/course_practice/course_practice_viewmodel.dart index 9641583..66e3860 100644 --- a/lib/ui/views/course_practice/course_practice_viewmodel.dart +++ b/lib/ui/views/course_practice/course_practice_viewmodel.dart @@ -16,7 +16,7 @@ class CoursePracticeViewModel extends BaseViewModel { final _navigationService = locator(); // Course practices - List _coursePractices = []; + List _coursePractices = []; List get coursePractices => _coursePractices; @@ -32,14 +32,10 @@ class CoursePracticeViewModel extends BaseViewModel { Future _getCoursePractice(int id) async { if (await _statusChecker.checkConnection()) { - Map data = { - 'owner_id':id, - 'owner_type':'SUB_COURSE' - }; + Map data = {'owner_id': id, 'owner_type': 'SUB_COURSE'}; _coursePractices = await _apiService.getCoursePractices(data); - rebuildUi(); - + rebuildUi(); } } } diff --git a/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart b/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart index d230842..1b5f957 100644 --- a/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart +++ b/lib/ui/views/course_subcategory/course_subcategory_viewmodel.dart @@ -41,8 +41,7 @@ class CourseSubcategoryViewModel extends BaseViewModel { if (await _statusChecker.checkConnection()) { _subcategories = await _apiService.getCourseSubcategories(id); - rebuildUi(); - + rebuildUi(); } } } diff --git a/lib/ui/views/duolingo/duolingo_view.dart b/lib/ui/views/duolingo/duolingo_view.dart index 1738122..032bc0c 100644 --- a/lib/ui/views/duolingo/duolingo_view.dart +++ b/lib/ui/views/duolingo/duolingo_view.dart @@ -195,7 +195,7 @@ class DuolingoView extends StackedView with $DuolingoView { ); Widget _buildBody(DuolingoViewModel viewModel) => IndexedStack( - index: viewModel.currentIndex, children: _buildScreens(viewModel)); + index: viewModel.currentPage, children: _buildScreens(viewModel)); List _buildScreens(DuolingoViewModel viewModel) => [ _buildDuolingoAssessmentsScreen(), diff --git a/lib/ui/views/duolingo/duolingo_viewmodel.dart b/lib/ui/views/duolingo/duolingo_viewmodel.dart index f9bd557..8e55004 100644 --- a/lib/ui/views/duolingo/duolingo_viewmodel.dart +++ b/lib/ui/views/duolingo/duolingo_viewmodel.dart @@ -18,9 +18,9 @@ class DuolingoViewModel extends FormViewModel { bool get isSpeaking => _isSpeaking; // In-app navigation - int _currentIndex = 0; + int _currentPage = 0; - int get currentIndex => _currentIndex; + int get currentPage => _currentPage; // Assessments Map _selectedAssessment = { @@ -200,15 +200,15 @@ class DuolingoViewModel extends FormViewModel { // In-app navigation void goTo(int page) { - _currentIndex = page; + _currentPage = page; rebuildUi(); } void goBack() { - if (_currentIndex == 0) { + if (_currentPage == 0) { pop(); } else { - _currentIndex--; + _currentPage--; rebuildUi(); } } diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index a0939eb..df1527b 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -17,6 +17,7 @@ class HomeView extends StackedView { @override void onViewModelReady(HomeViewModel viewModel) async { + // Removable await _init(viewModel); super.onViewModelReady(viewModel); } @@ -37,7 +38,7 @@ class HomeView extends StackedView { Widget _buildStartUpView() => const StartupView(label: 'Checking user info'); Widget _buildScaffold(HomeViewModel viewModel) => Scaffold( - body: getViewForIndex(viewModel.currentIndex), + body: getViewForIndex(viewModel.currentPage), bottomNavigationBar: _buildBottomNav(viewModel), ); @@ -47,7 +48,7 @@ class HomeView extends StackedView { selectedItemColor: kcPrimaryColor, backgroundColor: kcBackgroundColor, type: BottomNavigationBarType.fixed, - currentIndex: viewModel.currentIndex, + currentIndex: viewModel.currentPage, ); List _buildNavBarItems() => [ diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 87faf77..7a61fa3 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -33,13 +33,13 @@ class HomeViewModel extends ReactiveViewModel { UserModel? get user => _user; // Bottom navigation - int _currentIndex = 0; + int _currentPage = 0; - int get currentIndex => _currentIndex; + int get currentPage => _currentPage; // Bottom navigation void setCurrentIndex(int index) { - _currentIndex = index; + _currentPage = index; rebuildUi(); } diff --git a/lib/ui/views/learn/learn_viewmodel.dart b/lib/ui/views/learn/learn_viewmodel.dart index d6dd788..b8fba5e 100644 --- a/lib/ui/views/learn/learn_viewmodel.dart +++ b/lib/ui/views/learn/learn_viewmodel.dart @@ -23,19 +23,19 @@ class LearnViewModel extends ReactiveViewModel { final List> _learnLevels = [ { 'title': 'Beginner', - 'status': ProgressStatuses.completed, + 'status': ProgressStatuses.started, 'subtitle': 'Start your journey with the basics of English.', }, - { - 'title': 'Intermediate', - 'status': ProgressStatuses.started, - 'subtitle': 'Practice real conversations and expand vocabulary.', - }, - { - 'title': 'Advanced', - 'status': ProgressStatuses.pending, - 'subtitle': 'Achieve fluency and master complex topics.', - }, + // { + // 'title': 'Intermediate', + // 'status': ProgressStatuses.started, + // 'subtitle': 'Practice real conversations and expand vocabulary.', + // }, + // { + // 'title': 'Advanced', + // 'status': ProgressStatuses.pending, + // 'subtitle': 'Achieve fluency and master complex topics.', + // }, ]; List> get learnLevels => _learnLevels; diff --git a/lib/ui/views/learn_lesson/learn_lesson_view.dart b/lib/ui/views/learn_lesson/learn_lesson_view.dart index 058ceca..1fe0a8b 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_view.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_view.dart @@ -12,12 +12,30 @@ import '../../widgets/small_app_bar.dart'; import 'learn_lesson_viewmodel.dart'; class LearnLessonView extends StackedView { - const LearnLessonView({Key? key}) : super(key: key); + final String title; + final String topics; + final String subtitle; + final String description; + final List> 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; + return SizedBox( + height: half + 275 - half, + ); + } @override - LearnLessonViewModel viewModelBuilder( - BuildContext context, - ) => + LearnLessonViewModel viewModelBuilder(BuildContext context) => LearnLessonViewModel(); @override @@ -26,27 +44,38 @@ class LearnLessonView extends StackedView { LearnLessonViewModel viewModel, Widget? child, ) => - _buildScaffoldWrapper(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildScaffoldWrapper(LearnLessonViewModel viewModel) => Scaffold( + Widget _buildScaffoldWrapper( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffold(context: context, viewModel: viewModel), ); - Widget _buildScaffold(LearnLessonViewModel viewModel) => - SafeArea(child: _buildBody(viewModel)); + Widget _buildScaffold( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + SafeArea(child: _buildBody(context: context, viewModel: viewModel)); - Widget _buildBody(LearnLessonViewModel viewModel) => Padding( + Widget _buildBody( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + Padding( padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(viewModel), + child: _buildColumn(context: context, viewModel: viewModel), ); - Widget _buildColumn(LearnLessonViewModel viewModel) => Column( + Widget _buildColumn( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + Column( children: [ verticalSpaceMedium, _buildAppBar(viewModel), verticalSpaceMedium, - _buildLevelsColumnWrapper(viewModel), + _buildLevelsColumnWrapper(context: context, viewModel: viewModel), ], ); @@ -55,62 +84,99 @@ class LearnLessonView extends StackedView { showBackButton: true, ); - Widget _buildLevelsColumnWrapper(LearnLessonViewModel viewModel) => - Expanded(child: _buildLevelsColumnScrollView(viewModel)); + Widget _buildLevelsColumnWrapper( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + Expanded( + child: _buildLevelsColumnScrollView( + context: context, viewModel: viewModel)); - Widget _buildLevelsColumnScrollView(LearnLessonViewModel viewModel) => + Widget _buildLevelsColumnScrollView( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => SingleChildScrollView( - child: _buildLevelsColumn(viewModel), + child: _buildLevelsColumn(context: context, viewModel: viewModel), ); - Widget _buildLevelsColumn(LearnLessonViewModel viewModel) => Column( + Widget _buildLevelsColumn( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: _buildLevelsColumnChildren(viewModel), + children: + _buildLevelsColumnChildren(context: context, viewModel: viewModel), ); - List _buildLevelsColumnChildren(LearnLessonViewModel viewModel) => [ + List _buildLevelsColumnChildren( + {required BuildContext context, + required LearnLessonViewModel viewModel}) => + [ verticalSpaceMedium, _buildTitle(), verticalSpaceTiny, _buildSubtitle(), verticalSpaceSmall, - _buildModuleProgress(), - verticalSpaceMedium, - _buildContinueButton(), - verticalSpaceMedium, - _buildMotivationCard(), - verticalSpaceMedium, - _buildHeader(), - verticalSpaceMedium, - _buildListView(viewModel), - verticalSpaceMedium + _buildTopics(), + verticalSpaceSmall, + // _buildModuleProgress(), + // verticalSpaceMedium, + // _buildContinueButton(), + // verticalSpaceMedium, + // _buildMotivationCard(), + // verticalSpaceMedium, + //_buildHeader(), + //verticalSpaceMedium, + // _buildListView(viewModel), + getPadding(context), + _buildStartButton(viewModel), + verticalSpaceSmall, + _buildPracticeButton(viewModel) ]; Widget _buildTitle() => Text( - 'Module 1: Greetings & Introductions', + title, style: style16DG600, ); Widget _buildSubtitle() => Text( - 'Learn how to introduce yourself, talk about your surroundings, and start simple conversations.', - style: style14DG400, + subtitle, + style: style14DG600, + ); + + Widget _buildTopics() => Text( + topics, + style: style14DG500, ); Widget _buildModuleProgress() => const ModuleProgress(); - Widget _buildContinueButton() => const CustomElevatedButton( + Widget _buildStartButton(LearnLessonViewModel viewModel) => + CustomElevatedButton( height: 55, borderRadius: 12, + text: 'Start $title', foregroundColor: kcWhite, - text: 'Continue Lesson 1.3', 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( - 'Module 1: Greetings & Introductions', + title, style: style18DG700, ); @@ -122,9 +188,9 @@ class LearnLessonView extends StackedView { title: viewModel.lessons[index]['title'], status: viewModel.lessons[index]['status'], thumbnail: viewModel.lessons[index]['thumbnail'], - onLessonTap: () async => - await viewModel.navigateToLearnLessonDetail(), - onPracticeTap: () async => await viewModel.navigateToLearnPractice(), + onLessonTap: () async => await viewModel.navigateToLearnLessonDetail( + title: title, practices: practices, description: description), + // onPracticeTap: () async => await viewModel.navigateToLearnPractice(), ), ); diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index 19c40be..1cb08ac 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -32,14 +32,19 @@ class LearnLessonViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnLessonDetail() async => - await _navigationService.navigateToLearnLessonDetailView(); + Future navigateToLearnLessonDetail( + {required String title, + required List> practices, + required String description}) async => + await _navigationService.navigateToLearnLessonDetailView( + title: title, practices: practices, description: description); - Future navigateToLearnPractice() async => + Future navigateToLearnPractice( + List> practices) async => await _navigationService.navigateToLearnPracticeView( - buttonLabel: 'Start Practice', - title: 'Let \'s practice what you just learnt!', - subtitle: - 'I’ll ask you a few questions, and you can respond naturally.', + practices: practices, + title: 'Let’s Practice', + buttonLabel: 'Begin Lesson Practice', + subtitle: 'Let’s quickly review what you’ve learned in this lesson!', ); } diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart index 8ab958f..825a1fa 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart @@ -11,19 +11,28 @@ import '../../widgets/small_app_bar.dart'; import 'learn_lesson_detail_viewmodel.dart'; class LearnLessonDetailView extends StackedView { - const LearnLessonDetailView({Key? key}) : super(key: key); + final String title; + final String description; + final List> practices; + + const LearnLessonDetailView( + {Key? key, + required this.title, + required this.practices, + required this.description}) + : super(key: key); Future _navigate(LearnLessonDetailViewModel viewModel) async { await viewModel.pause(); - await viewModel.navigateToLearnPractice(); + await viewModel.navigateToLearnPractice(practices); } - // @override - // void onDispose(LearnLessonDetailViewModel viewModel) { - // print('DISPOSED'); - // viewModel.dispose(); - // super.onDispose(viewModel); - // } + @override + void onDispose(LearnLessonDetailViewModel viewModel) { + print('DISPOSED'); + viewModel.close(); + super.onDispose(viewModel); + } @override void onViewModelReady(LearnLessonDetailViewModel viewModel) async { @@ -116,7 +125,7 @@ class LearnLessonDetailView extends StackedView { ); Widget _buildTitle() => Text( - '1.3 Common Greetings', + title, style: style16DG600, ); @@ -149,7 +158,7 @@ class LearnLessonDetailView extends StackedView { ); Widget _buildDescription() => Text( - 'In this lesson, you’ll explore how to start simple conversations by greeting others in polite and friendly ways. You’ll practice different greetings for morning, afternoon, and evening, as well as casual and formal situations. By the end, you’ll know how to confidently say hello, ask how someone is, and respond naturally.', + description, style: style14DG600, ); diff --git a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart index 394a0da..a30e1d6 100644 --- a/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart @@ -55,18 +55,18 @@ class LearnLessonDetailViewModel extends BaseViewModel { await _chewieController?.pause(); } - @override - void dispose() { + void close() { _videoPlayerController?.dispose(); _chewieController?.dispose(); - super.dispose(); } // Navigation void pop() => _navigationService.back(); - Future navigateToLearnPractice() async => + Future navigateToLearnPractice( + List> practices) async => await _navigationService.navigateToLearnPracticeView( + practices: practices, buttonLabel: 'Start Practice', title: 'Let \'s practice what you just learnt!', subtitle: diff --git a/lib/ui/views/learn_level/learn_level_viewmodel.dart b/lib/ui/views/learn_level/learn_level_viewmodel.dart index 4163493..5666511 100644 --- a/lib/ui/views/learn_level/learn_level_viewmodel.dart +++ b/lib/ui/views/learn_level/learn_level_viewmodel.dart @@ -13,11 +13,11 @@ class LearnLevelViewModel extends BaseViewModel { 'current': true, 'subtitle': 'Start your journey with the basics of English.', }, - { - 'title': 'A2', - 'current': false, - 'subtitle': 'Build upon your foundational knowledge.', - }, + // { + // 'title': 'A2', + // 'current': false, + // 'subtitle': 'Build upon your foundational knowledge.', + // }, ]; List> get learnSubLevels => _learnSubLevels; @@ -27,11 +27,4 @@ class LearnLevelViewModel extends BaseViewModel { Future navigateToLearnModule() async => _navigationService.navigateToLearnModuleView(); - - Future navigateToLearnPractice() async => - await _navigationService.navigateToLearnPracticeView( - title: 'Let’s Practice Level 1', - buttonLabel: 'Begin Level Practice', - subtitle: 'Let’s quickly review what you’ve learned in this level! ', - ); } diff --git a/lib/ui/views/learn_module/learn_module_view.dart b/lib/ui/views/learn_module/learn_module_view.dart index c868e92..ba8388a 100644 --- a/lib/ui/views/learn_module/learn_module_view.dart +++ b/lib/ui/views/learn_module/learn_module_view.dart @@ -94,17 +94,25 @@ class LearnModuleView extends StackedView { itemBuilder: (context, index) => _buildTile( title: viewModel.modules[index]['title'], status: viewModel.modules[index]['status'], - subtitle: viewModel.modules[index]['subtitle']), + topics: viewModel.modules[index]['topics'], + subtitle: viewModel.modules[index]['subtitle'], + practices: viewModel.modules[index]['practices'], + description: viewModel.modules[index]['description']), ); - Widget _buildTile({ - required String title, - required String subtitle, - required ProgressStatuses status, - }) => + Widget _buildTile( + {required String title, + required String topics, + required String subtitle, + required String description, + required ProgressStatuses status, + required List> practices}) => LearnModuleTile( title: title, status: status, + topics: topics, subtitle: subtitle, + practices: practices, + description: description, ); } diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index 819182f..0b9bc30 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -11,26 +11,337 @@ class LearnModuleViewModel extends BaseViewModel { // Modules final List> _modules = [ { - 'status': ProgressStatuses.completed, - 'title': 'Module 1: Greetings & Introductions', - 'subtitle': - 'Learn how to introduce yourself, talk about your surroundings, and start simple conversations.', + 'status': ProgressStatuses.started, + 'title': 'Lesson 1.1', + 'subtitle': 'Start Speaking English Today! Greetings & Introductions', + 'topics': + """👉 How to use "Good Morning," "Afternoon," and "Evening" at the right time. + 👉 Why saying "I'm" is often better than "I am" in spoken English. + 👉 Master "My," "Your," and the verb "To Be" without the headache. + 👉 How to perfectly say the "Long I" sound in "Hi" and "My." + """, + 'practices': [ + { + 'question_text': 'Good morning! How are you?', + 'question_audio_url': + 'https://drive.google.com/file/d/1El9hhmZvLnrTYtVreHR0EDXNrZyGphT9/view?usp=sharing', + 'sample_answer': + 'https://drive.google.com/file/d/1dUSahuj_VdunV293gEZr0XL9d4WV7_8G/view?usp=sharing' + }, + { + 'question_text': 'What\'s your name?', + 'sample_answer': + 'https://drive.google.com/file/d/14oAqcMRltXeQhQ-RTGizO2DZ4CkHmKdu/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1iAsOIXD4NcsctKuubnvWlnwXodTMlou5/view?usp=drive_link' + }, + { + 'question_text': 'Nice to meet you!', + 'question_audio_url': + 'https://drive.google.com/file/d/10bOaNCcpNFzxpJ4d2-5KYahMCCcWt668/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1h8V6lFuOiOf0zRRN-NvYjtLrmMdx0SeX/view?usp=drive_link' + }, + { + 'question_text': + 'You are doing great! Tell me, are you happy to be here?', + 'sample_answer': + 'https://drive.google.com/file/d/1sjNFofDRr9KB8fQptdM12LDJ5DIaHujs/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1kcNIL5NCt_lfTzAbmCkM3yojD_UzjSx7/view?usp=drive_link', + }, + ], + 'description': + """Stop feeling nervous when you meet someone new! In this lesson, we break down the most common English greetings and show you exactly how to introduce yourself with confidence. + Whether you are at the market, in a classroom, or meeting someone online, the first 10 seconds of a conversation are the most important. Our teacher will guide you through the exact phrases you need to sound natural and friendly from the very first moment. + """, }, { 'status': ProgressStatuses.started, - 'title': 'Module 2: Everyday Basics', - 'subtitle': 'Learn numbers, colors, and common objects.', + 'title': 'Lesson 1.2', + 'subtitle': 'Talk About Your Home! "Where Are You From?" & Locations', + 'topics': + """👉 The difference between "Where are you from?" and "Where do you live?" + 👉 How to use "Am," "Is," and "Are" to talk about your home. + 👉 Learn to say "Where-are-you" as one smooth sound (word linking). + 👉 Practice a full conversation with Miss Alem. + 👉 A fast-paced game to test your speed and memory. + """, + 'practices': [ + { + 'question_text': + 'Hey, It is a pleasure to meet you. Where are you from?', + 'sample_answer': + 'https://drive.google.com/file/d/1F98PdPqeDhkF5KMzFjgmUoy4HanIDHVs/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1vipFTk-DYgOCYhyDyWVaORec7g0JeidD/view?usp=drive_link', + }, + { + 'question_text': 'Ethiopia is beautiful! What city are you from?', + 'sample_answer': + 'https://drive.google.com/file/d/1nZJLV9lOgFGqYr-W3vlpt8rbay_bwARJ/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1T8JIYQ6T9Mq_7TazGr85ag4PCyQgHhzi/view?usp=drive_link' + }, + { + 'question_text': 'I see. And where does your family live now?', + 'question_audio_url': + 'https://drive.google.com/file/d/19XAbHL3HqTpPcolvOQUVPSGef-Ythusu/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1O8o11DNKYZBcm1-f8pjMFJjNIvGmawvg/view?usp=drive_link' + }, + ], + 'description': + """Are you ready to talk about your city, town, or country with pride? In Lesson 1.2, we move past basic greetings and learn how to share where you come from and where you live now. + Whether you are meeting a tourist, a new classmate, or a colleague, being able to say "I’m from Ethiopia" or "I live in Addis Ababa" is the best way to build a connection. Miss Alem will show you how to link your words together so you sound like a natural English speaker! + """, }, { - 'title': 'Module 3: At the Cafe', - 'status': ProgressStatuses.pending, - 'subtitle': 'Practice ordering food and drinks confidently.', + 'title': 'Lesson 1.3', + 'status': ProgressStatuses.started, + 'subtitle': 'Talk About Your Family! Master "Have" vs. "Has"', + 'topics': + """👉 Master the names for parents, grandparents, and the secret word for brothers and sisters (Siblings). + 👉 Never mix up "I have" and "She has" ever again. + 👉 Why the letter "S" is the most important sound when talking about your brothers. + 👉 A close-up look at how to pronounce "Mother" and "Father" like a native speaker. + 👉 Test your eyes and ears by finding the hidden mistakes in our family game. + """, + 'practices': [ + { + 'question_text': + 'Hello! I want to know about your family. Do you have a brother or sister?', + 'sample_answer': + 'https://drive.google.com/file/d/1fred7Y5ocdD4codZuK7Pr349O38UkB0Z/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1tMoTA4D6joz0bkWS5Nfn-neUvbej6XDI/view?usp=drive_link', + }, + { + 'question_text': 'That is nice! How many people are in your family?', + 'sample_answer': + 'https://drive.google.com/file/d/1dhYhiycWwdtW0ndJUMdMXxxVyrXLXPCM/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1FwRxWvrxzKbhEIYImy2ONzd410pabkJa/view?usp=drive_link' + }, + { + 'question_text': 'Do you have grandparents?', + 'question_audio_url': + 'https://drive.google.com/file/d/1UBaQYiJINgOmZWDLFKeXZQ8Zfb7cOKNZ/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/14cnEJTO7GtGZcO8ag50JdupV2EkvDHes/view?usp=drive_link' + }, + ], + 'description': + """Do you have brothers and sisters? In Lesson 1.3, we dive into the most personal and important topic for building friendships: Your Family. Many beginners make mistakes with the words "have" and "has," but today, we will fix that! Our teacher will teach you the exact formulas to describe your parents, siblings, and grandparents with perfect grammar. By the end of this video, you will be able to introduce your entire family in English with confidence.""", }, { - 'progress': 0, - 'status': ProgressStatuses.pending, - 'title': 'Module 4: Asking for Directions', - 'subtitle': 'Learn numbers, colors, and common objects.', + 'status': ProgressStatuses.started, + 'title': 'Lesson 1.4', + 'subtitle': '"What Do You Do?" Talk About Your Job & Studies', + 'topics': + """👉 The clear difference between talking about work and academic life. + 👉 Simple rules to decide between "I’m a teacher" and "I’m an accountant." + 👉 How to use the "-ing" form if you are still training for your dream job. + 👉 Perfecting the final "T" in "Student" and the "SH" sound in "Cashier." + 👉 A high-energy game to test your grammar reflexes!""", + 'practices': [ + { + 'question_text': 'It is good to see you again! What do you do?', + 'question_audio_url': + 'https://drive.google.com/file/d/171fh_iN0aXS0t95_5RzcxFiVmLCB7caP/view?usp=sharing', + 'sample_answer': + 'https://drive.google.com/file/d/1wDkbp23F2PsLZM9mWlRd-JjnwZa97T-8/view?usp=drive_link', + }, + { + 'question_text': + 'That is great. Are you studying to be a professional? What are you studying?', + 'sample_answer': + 'https://drive.google.com/file/d/1egHUMIqGt9VAw5HMVMdjWR3zmamdSB6F/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1T_qrH_-KHgQFw5kX4Ti-UwXvnBFyJsWT/view?usp=drive_link' + }, + { + 'question_text': + 'Your sister works in a restaurant. Is she a waitress?', + 'question_audio_url': + 'https://drive.google.com/file/d/1Okd-W2zRZCukQfnbLEp9A4r_5rt99U_6/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1t-_JHXel9dtSHRwYU_CO1Mc8-Pa1pOa0/view?usp=drive_link' + }, + { + 'question_text': 'My friend works in a bank. Is he an accountant?', + 'question_audio_url': + 'https://drive.google.com/file/d/1K4ahmIzuBdrsUoO-gVRp525ZFyP7b1Cq/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1ecpWSQTvU9pu8BYq3qRkYUjblX5wG4RZ/view?usp=drive_link' + }, + ], + 'description': + """You’ve shared your name and your home—now it’s time to talk about your daily life! In Lesson 1.4, we master the most common question in English: "What do you do?" Whether you are working a full-time job, searching for a new career, or currently studying, this lesson gives you the exact phrases to describe your profession with perfect grammar. +Miss [Name] breaks down the tricky "A vs. An" rule so you never have to second-guess yourself again. Plus, learn how to sound more natural by "linking" your words like a native speaker! +""" + }, + { + 'title': 'Lesson 1.5', + 'status': ProgressStatuses.started, + 'subtitle': 'Talk About Your Day! Daily Routines & Time Words', + 'topics': + """👉 Master the 6 most common actions, from "Wake up" to "Go to bed." + 👉 Learn why we say "I eat" but "She eats" (and how to never forget it!). + 👉 Use "First," "Then," and "Finally" to tell a complete story about your day. + 👉 How to perfectly say the tricky "TH" sound in the word "Teeth." + 👉 A fast-paced game to test if your brain can handle the "S" rule under pressure!""", + 'practices': [ + { + 'question_text': + 'I want to hear about your day! What is the first thing you do in the morning?', + 'question_audio_url': + 'https://drive.google.com/file/d/1B_g45crvD0X2xfJJuCBdBhe7LNRJtKpa/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/13nV3uQ6Vftc0DznBJ8GQMi26dHxmMUwH/view?usp=drive_link', + }, + { + 'question_text': + 'That is a great start. What do you do for your hygiene? I brush...', + 'sample_answer': + 'https://drive.google.com/file/d/1ijbKHO9A3PEOUb712ycCw-P3ZvWX-NTp/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1r92ixsWSD58TrV9LIArPRaKk1bVE5m44/view?usp=drive_link' + }, + { + 'question_text': + 'Perfect. And then, what do you do before you leave the house?', + 'question_audio_url': + 'https://drive.google.com/file/d/1xEHuliUwyqFV35o_C2varAHcoI3QrOsS/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1RJgS3tp0ZKQ1YIt9fLRch-IqFialZR6Q/view?usp=drive_link' + }, + { + 'question_text': + 'And finally, what is the last thing you do at night?', + 'question_audio_url': + 'https://drive.google.com/file/d/1njWyQExmgAPWgDTrcpqitEoNjiPfCbEw/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/140O0xAkYpVo5FLZQmbc1BaY_6agYWeki/view?usp=drive_link' + }, + ], + 'description': + """What do you do from the moment you wake up until you go to bed? In Lesson 1.5, we master the art of describing your Daily Routine at Home. This is the best way to practice the "Simple Present Tense" so you can talk about your habits, chores, and schedules like a pro. +Our teacher will show you the "S" secret—the most important grammar rule for talking about other people—and teach you the "Time Order Words" that make your English flow naturally from one activity to the next. +""" + }, + { + 'title': 'Lesson 1.6', + 'status': ProgressStatuses.started, + 'subtitle': 'Express Your Feelings! Likes, Dislikes & Hobbies', + 'topics': + """👉 How to build perfect positive and negative sentences (I like vs. I don't like). + 👉 Master the "Do you like...?" structure and the correct short answers. + 👉 Learn why we switch from "Don't" to "Doesn't" for He and She. + 👉 How to master the "L" sound and make your "Don't" sound natural and fast. + 👉 A high-speed game to test if you can handle the grammar of likes and dislikes!""", + 'practices': [ + { + 'question_text': + 'I want to know about your hobbies. Do you like football?', + 'question_audio_url': + 'https://drive.google.com/file/d/1ombhL58UTSSMlVr4OkjEN2m1TXeIs3Jw/view?usp=sharing', + 'sample_answer': + 'https://drive.google.com/file/d/1E-NilYZkazwTyAFwwNunDQmWIxBHH_GL/view?usp=drive_link', + }, + { + 'question_text': + 'That’s fun! Some people prefer quiet things. What do you like to do at home', + 'sample_answer': + 'https://drive.google.com/file/d/1HJqwHQId9MUhaQvjoyhuhk-ZQNCz5-RP/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1HJqwHQId9MUhaQvjoyhuhk-ZQNCz5-RP/view?usp=drive_link' + }, + { + 'question_text': + 'Interesting. Many people like music. Do you like slow music?', + 'question_audio_url': + 'https://drive.google.com/file/d/13edPllK_dZYmmFIsue_A-sGmDe9fh89K/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1lA18zdiFbKKXaGwUvJv4JwBMW_Vxxkip/view?usp=drive_link' + }, + { + 'question_text': 'Do you like running?', + 'question_audio_url': + 'https://drive.google.com/file/d/1MSVceVXL-mAELGvv0_SN68t1-pCM-V6R/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1XkwoWINsoXDSe63kjJXQFczjtPKbBTY4/view?usp=drive_link' + }, + ], + 'description': + """What makes you happy? What do you avoid? In Lesson 1.6, we learn how to make your English conversations personal and fun by sharing your Likes and Dislikes. This is the final topic of Module 1, and it is the key to building real friendships by finding common interests! +We will see how to use the power of "Do" and "Don't," and show you how the "S" rule changes when we talk about what our friends like. Get ready to talk about football, coffee, music, and more! +""" + }, + { + 'title': 'Lesson 1.7', + 'status': ProgressStatuses.started, + 'subtitle': 'Module 1 Final Test! Speaking Review & Graduation', + 'topics': """👉 Fast-paced practice for names, origins, and jobs. + Mastering "Have," "Has," and the plural "S" under pressure. + 👉 Using the Simple Present Tense to describe your day and your likes. + 👉 A bonus challenge to find and correct common beginner mistakes. + 👉 Two speaking scripts to help you link your words and sound like a native speaker.""", + 'practices': [ + { + 'question_text': 'Hello! What\'s your name?', + 'question_audio_url': + 'https://drive.google.com/file/d/11xsnZpqOXQREc2nku77u_qOXucUdncDq/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1OgvhumoPNdw33WfxEZfS1xtYZHshAM-k/view?usp=drive_link', + }, + { + 'question_text': 'It was a pleasure meeting you. Nice to meet you!', + 'sample_answer': + 'https://drive.google.com/file/d/1NmKeyx0mKmbFyZdmQ4sWGM6UMpwAIrHN/view?usp=drive_link', + 'question_audio_url': + 'https://drive.google.com/file/d/1nu1fiK4dLCjW9jjENvThshU48vFj-T3h/view?usp=drive_link' + }, + { + 'question_text': 'Where are you from?', + 'question_audio_url': + 'https://drive.google.com/file/d/1uM2L6-u0H-LHyDQL_Y0txjoO64n5lHkp/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1VXhHRG2CJR3lx07cAuiZrfCxzxdOcCIz/view?usp=drive_link' + }, + { + 'question_text': 'And where do you live now?', + 'question_audio_url': + 'https://drive.google.com/file/d/1sGJpaRQ1wArA2iDq7BZM-56abMZwiISO/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/10Cj8r2KeYUttBJ7W2wETP8XSdK2ZItom/view?usp=drive_link' + }, + { + 'question_text': 'Do you have a brother or sister?', + 'question_audio_url': + 'https://drive.google.com/file/d/1cA4wD7zY7WRgorWladvf1f8QRu54oBBw/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1IeiGyN1S5XaREfXgmzfl-zESap8A_k3H/view?usp=drive_link' + }, + { + 'question_text': 'I am a teacher. Are you a student?', + 'question_audio_url': + 'https://drive.google.com/file/d/1n5w3skHBvQ2hE_OA2HdeUnr0YeN4zL2_/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1j86TVmeNzZvL8RueZeIueCQO_40i9aMh/view?usp=drive_link' + }, + { + 'question_text': 'What is the first thing you do in the morning?', + 'question_audio_url': + 'https://drive.google.com/file/d/1oCrDDtYF4-1S2UYu6shilqmQSqyRG3v_/view?usp=drive_link', + 'sample_answer': + 'https://drive.google.com/file/d/1shpCr2XvV12cXaxuJ-AjW-0XWvBdtT8Q/view?usp=drive_link' + }, + ], + 'description': + """Congratulations! You have reached the end of Module 1. You started with simple greetings, and now you are ready to hold a full conversation in English! This is our Final Speaking Review, where we test everything you’ve learned so far. +From your name and family to your daily habits and hobbies, this lesson is designed to push your speed, accuracy, and confidence. We will lead you through three high-intensity speaking drills and a bonus Grammar Check to make sure you are 100% ready for Module 2! +""" }, ]; @@ -39,13 +350,25 @@ class LearnModuleViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); - Future navigateToLearnLesson() async => - await _navigationService.navigateToLearnLessonView(); + Future navigateToLearnLesson( + {required String title, + required String topics, + required String subtitle, + required String description, + required List> practices}) async => + await _navigationService.navigateToLearnLessonView( + title: title, + topics: topics, + subtitle: subtitle, + practices: practices, + description: description); - Future navigateToLearnPractice() async => + Future navigateToLearnPractice( + List> practices) async => await _navigationService.navigateToLearnPracticeView( - title: 'Let’s Practice Module 1', - buttonLabel: 'Begin Module Practice', - subtitle: 'Let’s quickly review what you’ve learned in this module! ', + practices: practices, + title: 'Let’s Practice', + buttonLabel: 'Begin Lesson Practice', + subtitle: 'Let’s quickly review what you’ve learned in this lesson!', ); } diff --git a/lib/ui/views/learn_practice/learn_practice_view.dart b/lib/ui/views/learn_practice/learn_practice_view.dart index ec50995..80393fc 100644 --- a/lib/ui/views/learn_practice/learn_practice_view.dart +++ b/lib/ui/views/learn_practice/learn_practice_view.dart @@ -3,26 +3,57 @@ import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/finish_learn_practice_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart'; -import 'package:yimaru_app/ui/views/learn_practice/screens/listen_learn_practice_speaker_screen.dart'; +import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practices_screen.dart'; +import 'package:yimaru_app/ui/views/learn_practice/screens/interact_learn_practice_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_intro_screen.dart'; -import 'package:yimaru_app/ui/views/learn_practice/screens/speak_to_learn_practice_listener_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart'; import '../../common/app_colors.dart'; +import '../../widgets/cancel_learn_practice_sheet.dart'; import 'learn_practice_viewmodel.dart'; class LearnPracticeView extends StackedView { final String title; final String subtitle; final String buttonLabel; + final List> practices; const LearnPracticeView( {Key? key, required this.title, required this.subtitle, + required this.practices, required this.buttonLabel}) : super(key: key); + Future _cancel(LearnPracticeViewModel viewModel) async { + viewModel.pop(); + viewModel.goTo(0); + viewModel.stopRecording(); + } + + Future _pop( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) async { + { + if (viewModel.currentPage == 3) { + await _showSheet(context: context, viewModel: viewModel); + } else { + viewModel.goBack(); + } + } + } + + Future _showSheet( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) async => + await showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: kcTransparent, + builder: (_) => _buildSheet(viewModel), + ); + @override LearnPracticeViewModel viewModelBuilder(BuildContext context) => LearnPracticeViewModel(); @@ -33,36 +64,46 @@ class LearnPracticeView extends StackedView { LearnPracticeViewModel viewModel, Widget? child, ) => - _buildPracticeScreensWrapper(viewModel); + _buildPracticeScreensWrapper(context: context, viewModel: viewModel); - Widget _buildPracticeScreensWrapper(LearnPracticeViewModel viewModel) => + Widget _buildPracticeScreensWrapper( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => PopScope( - canPop: true, - onPopInvokedWithResult: (value, data) { - if (!value) return; - WidgetsBinding.instance - .addPostFrameCallback((_) => viewModel.goBack()); - }, + canPop: viewModel.currentPage == 0 ? true : false, + onPopInvokedWithResult: (value, data) async => + await _pop(context: context, viewModel: viewModel), child: _buildScaffoldWrapper(viewModel)); + Widget _buildSheet(LearnPracticeViewModel viewModel) => + CancelLearnPracticeSheet( + onClose: viewModel.pop, + onContinue: viewModel.pop, + onCancel: () async => await _cancel(viewModel), + ); + Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, body: _buildBody(viewModel), ); Widget _buildBody(LearnPracticeViewModel viewModel) => - IndexedStack(index: viewModel.currentIndex, children: _buildScreens()); + IndexedStack(index: viewModel.currentPage, children: _buildScreens()); List _buildScreens() => [ + _buildLearnPracticesScreen(), _buildLearnPracticeIntroScreen(), _buildStartLearnPracticeScreen(), - _buildListenLearnPracticeSpeakerScreen(), - _buildSpeakToLearnPracticeListenerScreen(), + _buildInteractLearnPracticeScreen(), _buildFinishLearnPracticeScreen(), _buildLearnPracticeResultScreen(), _buildLearnPracticeCompletionScreen() ]; + Widget _buildLearnPracticesScreen() => LearnPracticesScreen( + practices: practices, + ); + Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen( title: title, subtitle: subtitle, @@ -71,11 +112,8 @@ class LearnPracticeView extends StackedView { Widget _buildStartLearnPracticeScreen() => const StartLearnPracticeScreen(); - Widget _buildListenLearnPracticeSpeakerScreen() => - const ListenLearnPracticeSpeakerScreen(); - - Widget _buildSpeakToLearnPracticeListenerScreen() => - const SpeakToLearnPracticeListenerScreen(); + Widget _buildInteractLearnPracticeScreen() => + const InteractLearnPracticeScreen(); Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen(); diff --git a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart index 3c5fcc9..c4b7782 100644 --- a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -1,36 +1,200 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:waveform_recorder/waveform_recorder.dart'; +import 'package:yimaru_app/models/user_model.dart'; +import 'package:yimaru_app/services/authentication_service.dart'; +import 'package:yimaru_app/services/voice_recorder_service.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; +import '../../../services/audio_player_service.dart'; +import '../../common/app_colors.dart'; + +class LearnPracticeViewModel extends ReactiveViewModel { + // Dependency injection + final _dialogService = locator(); -class LearnPracticeViewModel extends BaseViewModel { final _navigationService = locator(); + final _audioPlayerService = locator(); + + final _voiceRecorderService = locator(); + + + final _authenticationService = locator(); + + LearnPracticeViewModel() { + _listenToAudio(); + } + + @override + List get listenableServices => + [_audioPlayerService, _voiceRecorderService,_authenticationService]; + + // User + UserModel? get _user => _authenticationService.user; + + UserModel? get user => _user; + + // AudioPlayer + AudioPlayer get _player => _audioPlayerService.player; + + AudioPlayer get player => _player; + + Duration _duration = Duration.zero; + + Duration _position = Duration.zero; + + Duration get position => _position; + + Duration get duration => _duration; + + double get progress { + if (_duration.inMilliseconds == 0) return 0; + return _position.inMilliseconds / _duration.inMilliseconds; + } + + // Voice recorder + + WaveformRecorderController get _waveController => + _voiceRecorderService.waveController; + + WaveformRecorderController get waveController => _waveController; + + // Voice recorder state + VoiceRecordingState get _recordingState => + _voiceRecorderService.recordingState; + + VoiceRecordingState get recordingState => _recordingState; + + + // Busy object + StateObjects _busyObject = StateObjects.none; + + StateObjects get busyObject => _busyObject; + // In-app navigation - int _currentIndex = 0; + int _currentPage = 0; - int get currentIndex => _currentIndex; + int get currentPage => _currentPage; - // Practice results - final List> _practiceResults = [ - {'question': 'What is your name?'}, - {'question': 'Where are you from?'}, - {'question': 'Where are you from?'} - ]; + // Practice + Map _selectedPractice = {}; - List> get practiceResults => _practiceResults; + Map get selectedPractice => _selectedPractice; + + + + // Practice + void setPractice(Map practice) { + _selectedPractice = practice; + goTo(1); + } + + + + // Play practice audio + Future playQuestionAudio() async => await runBusyFuture(_playQuestionAudio(), + busyObject: StateObjects.learnPracticeQuestion); + + Future _playQuestionAudio() async { + goTo(3); + await _audioPlayerService.playUrl(_selectedPractice['question_audio_url']); + } + + void _listenToAudio() { + _audioPlayerService.durationStream.listen((dur) { + _duration = dur; + print('DURATION: $_duration'); + rebuildUi(); + }); + + _audioPlayerService.positionStream.listen((pos) { + _position = pos; + print('POSITION: $_position'); + rebuildUi(); + }); + + } + + // Set busy object + + void setBusyObject(StateObjects object){ + _busyObject = object; + notifyListeners(); + } + + + // Sample audio + Future playSampleAudio() async => await runBusyFuture(_playSampleAudio(), + busyObject: StateObjects.learnPracticeSample); + Future _playSampleAudio() async { + setBusyObject(StateObjects.learnPracticeSample); + await _audioPlayerService.playUrl(_selectedPractice['sample_answer']); + } + Future pauseSampleAudio()async=> await runBusyFuture(_pauseSampleAudio(), + busyObject: StateObjects.learnPracticeSample); + + Future _pauseSampleAudio() async { + setBusyObject(StateObjects.learnPracticeSample); + await _audioPlayerService.pause(); + } + + + // Recorded audio + Future playRecordedAudio() async => await runBusyFuture(_playRecordedAudio(), + busyObject: StateObjects.learnPracticeAnswer); + + Future _playRecordedAudio() async { + setBusyObject(StateObjects.learnPracticeAnswer); + await _audioPlayerService.playLocal(await _voiceRecorderService.getRecordedAudio() ?? ''); + } + + Future pauseRecordedAudio()async=> await runBusyFuture(_pauseRecordedAudio(), + busyObject: StateObjects.learnPracticeAnswer); + + + Future _pauseRecordedAudio() async { + setBusyObject(StateObjects.learnPracticeAnswer); + await _audioPlayerService.pause(); + } + + + // Voice recorder + Future startRecording() async => await runBusyFuture(_startRecording(),busyObject: StateObjects.recordLearnPracticeAnswer ); + + Future _startRecording() async => await _voiceRecorderService.startRecording(); + + + Future stopRecording() async => + await _voiceRecorderService.stopRecording(); + + // Dialogue + Future showAbortDialog() async { + DialogResponse? response = await _dialogService.showDialog( + cancelTitle: 'No', + title: 'Recording', + buttonTitle: 'Yes', + barrierDismissible: true, + cancelTitleColor: kcDarkGrey, + buttonTitleColor: kcPrimaryColor, + description: 'Are you sure you want to stop recording?', + ); + return response?.confirmed; + } // In-app navigation void goTo(int page) { - _currentIndex = page; + _currentPage = page; rebuildUi(); } void goBack() { - if (_currentIndex == 0) { + if (_currentPage == 0) { pop(); } else { - _currentIndex--; + _currentPage--; rebuildUi(); } } diff --git a/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart index 75c1bd5..eafd866 100644 --- a/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/finish_learn_practice_screen.dart @@ -49,7 +49,7 @@ class FinishLearnPracticeScreen ); Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( - showBackButton: false, + showBackButton: true, onTap: viewModel.goBack, title: 'Practice Speaking', ); diff --git a/lib/ui/views/learn_practice/screens/listen_learn_practice_speaker_screen.dart b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart similarity index 50% rename from lib/ui/views/learn_practice/screens/listen_learn_practice_speaker_screen.dart rename to lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart index ed96009..f4d3acc 100644 --- a/lib/ui/views/learn_practice/screens/listen_learn_practice_speaker_screen.dart +++ b/lib/ui/views/learn_practice/screens/interact_learn_practice_screen.dart @@ -1,19 +1,38 @@ +import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:stacked/stacked.dart'; +import 'package:waveform_recorder/waveform_recorder.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/cancel_learn_practice_sheet.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; +import 'package:yimaru_app/ui/widgets/page_loading_indicator.dart'; import 'package:yimaru_app/ui/widgets/wave_wrapper.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_column_button.dart'; import '../../../widgets/small_app_bar.dart'; -class ListenLearnPracticeSpeakerScreen +class InteractLearnPracticeScreen extends ViewModelWidget { - const ListenLearnPracticeSpeakerScreen({super.key}); + const InteractLearnPracticeScreen({super.key}); + + Future _cancel(LearnPracticeViewModel viewModel) async { + viewModel.pop(); + viewModel.goTo(0); + viewModel.stopRecording(); + } + + void _start(LearnPracticeViewModel viewModel) { + viewModel.playQuestionAudio(); + } + + Future _stop(LearnPracticeViewModel viewModel) async { + await viewModel.stopRecording(); + viewModel.goTo(4); + } Future _showSheet( {required BuildContext context, @@ -40,28 +59,34 @@ class ListenLearnPracticeSpeakerScreen Widget _buildScaffold( {required BuildContext context, required LearnPracticeViewModel viewModel}) => - SafeArea( - child: - _buildBodyColumnWrapper(context: context, viewModel: viewModel)); - - Widget _buildBodyColumnWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBodyStack(context: context, viewModel: viewModel), - ); + SafeArea(child: _buildBodyStack(context: context, viewModel: viewModel)); Widget _buildBodyStack( {required BuildContext context, required LearnPracticeViewModel viewModel}) => Stack( children: [ - _buildBodyColumn(context: context, viewModel: viewModel), - _buildProgressIndicatorWrapper() + _buildBodyColumnWrapper(context: context, viewModel: viewModel), + _buildProgressIndicatorState(viewModel), + _buildSpeakerState(context: context, viewModel: viewModel) ], ); + Widget _buildSpeakerState( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => + viewModel.busy(StateObjects.learnPracticeQuestion) + ? const PageLoadingIndicator() + : Container(); + + Widget _buildBodyColumnWrapper( + {required BuildContext context, + required LearnPracticeViewModel viewModel}) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBodyColumn(context: context, viewModel: viewModel), + ); + Widget _buildBodyColumn( {required BuildContext context, required LearnPracticeViewModel viewModel}) => @@ -90,28 +115,79 @@ class ListenLearnPracticeSpeakerScreen Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( showBackButton: true, - onTap: viewModel.goBack, title: 'Practice Speaking', + onTap: () async => await _cancel(viewModel), ); Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, - children: _buildSpeakingIndicatorChildren(), + children: _buildSpeakingIndicatorChildren(viewModel), ); - List _buildSpeakingIndicatorChildren() => - [_buildSpeakerLabel(), verticalSpaceMedium, _buildSpeakingIndicator()]; + List _buildSpeakingIndicatorChildren( + LearnPracticeViewModel viewModel) => + [ + _buildSpeakerLabelState(viewModel), + verticalSpaceMedium, + _buildSpeakingIndicator(viewModel) + ]; - Widget _buildSpeakerLabel() => Text( + Widget _buildSpeakerLabelState(LearnPracticeViewModel viewModel) => + viewModel.player.state == PlayerState.playing + ? _buildListeningLabel() + : VoiceRecordingState.recording == viewModel.recordingState + ? _buildSpeakingLabel() + : const SizedBox(height: 20); + + Widget _buildListeningLabel() => Text( 'Daniel is speaking...', style: style14P400, textAlign: TextAlign.center, ); - Widget _buildSpeakingIndicator() => - WaveWrapper(height: 200, child: _buildSpinner()); + Widget _buildSpeakingLabel() => Text( + 'You\'re is speaking...', + style: style14P400, + textAlign: TextAlign.center, + ); + + Widget _buildSpeakingIndicator(LearnPracticeViewModel viewModel) => + WaveWrapper(height: 200, child: _buildSpinnerState(viewModel)); + + Widget _buildSpinnerState(LearnPracticeViewModel viewModel) => + viewModel.player.state == PlayerState.playing + ? _buildSpinner() + : VoiceRecordingState.recording == viewModel.recordingState && + viewModel.waveController.isRecording + ? _buildSpeakingSpinnerColumn(viewModel) + : Container(); + + Widget _buildSpeakingSpinnerColumn(LearnPracticeViewModel viewModel) => + Column( + mainAxisSize: MainAxisSize.min, + children: _buildSpeakingSpinnerColumnChildren(viewModel), + ); + + List _buildSpeakingSpinnerColumnChildren( + LearnPracticeViewModel viewModel) => + [_buildSpinner(), _buildSpeakingSpinnerContainer(viewModel)]; + + Widget _buildSpeakingSpinnerContainer(LearnPracticeViewModel viewModel) => + SizedBox( + width: 65, + child: _buildSpeakingSpinner(viewModel), + ); + + Widget _buildSpeakingSpinner(LearnPracticeViewModel viewModel) => + WaveformRecorder( + height: 35, + onRecordingStopped: () {}, + waveColor: kcPrimaryColor, + durationTextStyle: style14P600, + controller: viewModel.waveController, + ); Widget _buildSpinner() => const SpinKitWave( size: 20, @@ -168,28 +244,61 @@ class ListenLearnPracticeSpeakerScreen {required BuildContext context, required LearnPracticeViewModel viewModel}) => [ - _buildReplyButtonWrapper(), + _buildReplyButtonWrapper(viewModel), _buildMicButtonWrapper(viewModel), _buildCancelButtonWrapper(context: context, viewModel: viewModel) ]; - Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); + Widget _buildReplyButtonWrapper(LearnPracticeViewModel viewModel) => + Expanded(child: _buildReplyButton(viewModel)); - Widget _buildReplyButton() => const CustomColumnButton( - icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); + Widget _buildReplyButton(LearnPracticeViewModel viewModel) => + CustomColumnButton( + icon: Icons.replay, + label: 'Reply', + color: viewModel.recordingState == VoiceRecordingState.pending && + viewModel.player.state != PlayerState.playing + ? kcPrimaryColor + : kcLightGrey, + onTap: viewModel.recordingState == VoiceRecordingState.pending && + viewModel.player.state != PlayerState.playing + ? () => _start(viewModel) + : null, + ); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => Expanded(child: _buildMicButton(viewModel)); Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton( - onPressed: () => viewModel.goTo(3), - style: const ButtonStyle( - shape: WidgetStatePropertyAll(CircleBorder()), - padding: WidgetStatePropertyAll(EdgeInsets.all(15)), - shadowColor: WidgetStatePropertyAll(kcPrimaryColor), - backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), + style: ButtonStyle( + backgroundColor: WidgetStatePropertyAll( + viewModel.player.state == PlayerState.playing || + viewModel.busy(StateObjects.recordLearnPracticeAnswer) + ? kcVeryLightGrey + : kcPrimaryColor, + ), + shadowColor: const WidgetStatePropertyAll(kcWhite), + shape: const WidgetStatePropertyAll(CircleBorder()), + padding: const WidgetStatePropertyAll(EdgeInsets.all(15)), ), - child: _buildMicIcon(), + onPressed: () async => viewModel.player.state == PlayerState.playing || + viewModel.busy(StateObjects.recordLearnPracticeAnswer) + ? null + : viewModel.recordingState == VoiceRecordingState.pending + ? await viewModel.startRecording() + : await _stop(viewModel), + child: _buildMicState(viewModel), + ); + + Widget _buildMicState(LearnPracticeViewModel viewModel) => + viewModel.recordingState == VoiceRecordingState.pending + ? _buildMicIcon() + : _buildCheckIcon(); + + Widget _buildCheckIcon() => const Icon( + Icons.check, + size: 35, + color: kcWhite, ); Widget _buildMicIcon() => const Icon( @@ -217,18 +326,33 @@ class ListenLearnPracticeSpeakerScreen Widget _buildSheet(LearnPracticeViewModel viewModel) => CancelLearnPracticeSheet( - onTap: viewModel.pop, + onClose: viewModel.pop, + onContinue: viewModel.pop, + onCancel: () async => await _cancel(viewModel), ); - Widget _buildProgressIndicatorWrapper() => Positioned( + Widget _buildProgressIndicatorState(LearnPracticeViewModel viewModel) => + viewModel.recordingState == VoiceRecordingState.pending + ? _buildProgressIndicatorWrapper(viewModel) + : Container(); + + Widget _buildProgressIndicatorWrapper(LearnPracticeViewModel viewModel) => + Positioned( top: 75, left: 0, right: 0, - child: _buildProgressIndicator(), + child: _buildProgressIndicatorSpacer(viewModel), ); - Widget _buildProgressIndicator() => const CustomLinearProgressIndicator( - progress: 0.7, - activeColor: kcPrimaryColor, - backgroundColor: kcVeryLightGrey); + Widget _buildProgressIndicatorSpacer(LearnPracticeViewModel viewModel) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildProgressIndicator(viewModel), + ); + + Widget _buildProgressIndicator(LearnPracticeViewModel viewModel) => + CustomLinearProgressIndicator( + activeColor: kcPrimaryColor, + progress: viewModel.progress, + backgroundColor: kcVeryLightGrey); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart index 3440fac..b8495e8 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_completion_screen.dart @@ -77,7 +77,7 @@ class LearnPracticeCompletionScreen borderRadius: 12, text: 'Continue', foregroundColor: kcWhite, - onTap: () => viewModel.pop(), + onTap: () => viewModel.goTo(0), backgroundColor: kcPrimaryColor, ); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart index 26ce194..dc09648 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_intro_screen.dart @@ -128,7 +128,7 @@ class LearnPracticeIntroScreen extends ViewModelWidget { borderRadius: 12, text: buttonLabel, foregroundColor: kcWhite, - onTap: () => viewModel.goTo(1), + onTap: () => viewModel.goTo(2), backgroundColor: kcPrimaryColor, ); } diff --git a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart index 351e913..5eaa76b 100644 --- a/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart +++ b/lib/ui/views/learn_practice/screens/learn_practice_result_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart'; -import 'package:yimaru_app/ui/widgets/practice_results_wrapper.dart'; +import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; @@ -71,7 +71,7 @@ class LearnPracticeResultScreen ]; Widget _buildResultsSection(LearnPracticeViewModel viewModel) => - PracticeResultsWrapper(data: viewModel.practiceResults); + LearnPracticeResultsWrapper(data: viewModel.selectedPractice); Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection(); diff --git a/lib/ui/views/learn_practice/screens/learn_practices_screen.dart b/lib/ui/views/learn_practice/screens/learn_practices_screen.dart new file mode 100644 index 0000000..8ebd3cf --- /dev/null +++ b/lib/ui/views/learn_practice/screens/learn_practices_screen.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/widgets/learn_practice_card.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/small_app_bar.dart'; +import '../learn_practice_viewmodel.dart'; + +class LearnPracticesScreen extends ViewModelWidget { + final List> practices; + + const LearnPracticesScreen({Key? key, required this.practices}) + : super(key: key); + + @override + Widget build(BuildContext context, LearnPracticeViewModel viewModel) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(LearnPracticeViewModel viewModel) => + SafeArea(child: _buildBody(viewModel)); + + Widget _buildBody(LearnPracticeViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + verticalSpaceMedium, + _buildPracticeColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( + onTap: viewModel.pop, + showBackButton: true, + ); + + Widget _buildPracticeColumnWrapper(LearnPracticeViewModel viewModel) => + Expanded(child: _buildPracticeColumnScrollView(viewModel)); + + Widget _buildPracticeColumnScrollView(LearnPracticeViewModel viewModel) => + SingleChildScrollView( + child: _buildPracticeColumn(viewModel), + ); + + Widget _buildPracticeColumn(LearnPracticeViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildPracticeColumnChildren(viewModel), + ); + + List _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => + [ + verticalSpaceMedium, + _buildTitle(), + _buildSubtitle(), + verticalSpaceMedium, + _buildListView(viewModel) + ]; + + Widget _buildTitle() => Text( + 'Learn Practices', + style: style18DG700, + ); + + Widget _buildSubtitle() => Text( + 'Select a practice test your progress', + style: style14DG400, + ); + + Widget _buildListView(LearnPracticeViewModel viewModel) => GridView.builder( + shrinkWrap: true, + itemCount: practices.length, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) => + _buildCard(index: index, practice: practices[index]), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 15, + crossAxisSpacing: 15, + childAspectRatio: 1.45, + ), + ); + + Widget _buildCard( + {required int index, required Map practice}) => + LearnPracticeCard( + index: index + 1, + practice: practice, + ); +} diff --git a/lib/ui/views/learn_practice/screens/speak_to_learn_practice_listener_screen.dart b/lib/ui/views/learn_practice/screens/speak_to_learn_practice_listener_screen.dart deleted file mode 100644 index 688878a..0000000 --- a/lib/ui/views/learn_practice/screens/speak_to_learn_practice_listener_screen.dart +++ /dev/null @@ -1,233 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:stacked/stacked.dart'; -import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; -import 'package:yimaru_app/ui/widgets/cancel_learn_practice_sheet.dart'; -import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; - -import '../../../common/app_colors.dart'; -import '../../../common/ui_helpers.dart'; -import '../../../widgets/custom_column_button.dart'; -import '../../../widgets/small_app_bar.dart'; - -class SpeakToLearnPracticeListenerScreen - extends ViewModelWidget { - const SpeakToLearnPracticeListenerScreen({super.key}); - - Future _showSheet( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) async => - await showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: kcTransparent, - builder: (_) => _buildSheet(viewModel), - ); - - @override - Widget build(BuildContext context, LearnPracticeViewModel viewModel) => - _buildScaffoldWrapper(context: context, viewModel: viewModel); - - Widget _buildScaffoldWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffold(context: context, viewModel: viewModel), - ); - - Widget _buildScaffold( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - SafeArea( - child: - _buildBodyColumnWrapper(context: context, viewModel: viewModel)); - - Widget _buildBodyColumnWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBodyStack(context: context, viewModel: viewModel), - ); - - Widget _buildBodyStack( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Stack( - children: [ - _buildBodyColumn(context: context, viewModel: viewModel), - _buildProgressIndicatorWrapper() - ], - ); - - Widget _buildBodyColumn( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: - _buildBodyColumnChildren(context: context, viewModel: viewModel), - ); - - List _buildBodyColumnChildren( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - [ - _buildAppBarWrapper(viewModel), - _buildSpeakingIndicatorWrapper(viewModel), - _buildLowerButtonsSectionWrapper(context: context, viewModel: viewModel) - ]; - - Widget _buildAppBarWrapper(LearnPracticeViewModel viewModel) => Column( - children: [ - verticalSpaceMedium, - _buildAppBar(viewModel), - verticalSpaceMedium, - ], - ); - - Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( - onTap: viewModel.goBack, - showBackButton: true, - title: 'Practice Speaking', - ); - - Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: _buildSpeakingIndicatorChildren(), - ); - - List _buildSpeakingIndicatorChildren() => - [_buildSpeakerLabel(), verticalSpaceMedium, _buildSpeakingIndicator()]; - - Widget _buildSpeakerLabel() => Text( - 'You are speaking...', - style: style14P400, - textAlign: TextAlign.center, - ); - - Widget _buildSpeakingIndicator() => Container( - height: 100, - alignment: Alignment.center, - child: _buildSpinner(), - ); - - Widget _buildSpinner() => const SpinKitWave( - size: 75, - color: kcPrimaryColor, - type: SpinKitWaveType.center, - ); - - Widget _buildLowerButtonsSectionWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: - _buildLowerButtonsSection(context: context, viewModel: viewModel), - ); - - Widget _buildLowerButtonsSection( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildLowerButtonsSectionChildren( - context: context, viewModel: viewModel), - ); - - List _buildLowerButtonsSectionChildren( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - [ - _buildActionLabel(), - verticalSpaceMedium, - _buildButtonsRowWrapper(context: context, viewModel: viewModel), - verticalSpaceMedium, - ]; - - Widget _buildActionLabel() => Text( - 'Tap the microphone to speak', - style: style14DG400, - textAlign: TextAlign.center, - ); - - Widget _buildButtonsRowWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: - _buildButtonsRowChildren(context: context, viewModel: viewModel), - ); - - List _buildButtonsRowChildren( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - [ - _buildEmptySpace(), - _buildCheckButtonWrapper(viewModel), - _buildCancelButtonWrapper(context: context, viewModel: viewModel) - ]; - - Widget _buildEmptySpace() => Expanded(child: Container()); - - Widget _buildCheckButtonWrapper(LearnPracticeViewModel viewModel) => - Expanded(child: _buildCheckButton(viewModel)); - - Widget _buildCheckButton(LearnPracticeViewModel viewModel) => ElevatedButton( - onPressed: () => viewModel.goTo(4), - style: const ButtonStyle( - shape: WidgetStatePropertyAll(CircleBorder()), - padding: WidgetStatePropertyAll(EdgeInsets.all(15)), - shadowColor: WidgetStatePropertyAll(kcPrimaryColor), - backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), - ), - child: _buildCheckIcon(), - ); - - Widget _buildCheckIcon() => const Icon( - Icons.check, - size: 35, - color: kcWhite, - ); - - Widget _buildCancelButtonWrapper( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - Expanded( - child: _buildCancelButton(context: context, viewModel: viewModel)); - - Widget _buildCancelButton( - {required BuildContext context, - required LearnPracticeViewModel viewModel}) => - CustomColumnButton( - color: kcRed, - label: 'Cancel', - icon: Icons.close, - onTap: () async => - await _showSheet(context: context, viewModel: viewModel), - ); - - Widget _buildSheet(LearnPracticeViewModel viewModel) => - CancelLearnPracticeSheet( - onTap: viewModel.pop, - ); - - Widget _buildProgressIndicatorWrapper() => Positioned( - top: 75, - left: 0, - right: 0, - child: _buildProgressIndicator(), - ); - - Widget _buildProgressIndicator() => const CustomLinearProgressIndicator( - progress: 0.7, - activeColor: kcPrimaryColor, - backgroundColor: kcVeryLightGrey); -} diff --git a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart index bf7dbd7..2afee30 100644 --- a/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart +++ b/lib/ui/views/learn_practice/screens/start_learn_practice_screen.dart @@ -10,6 +10,10 @@ import '../../../widgets/small_app_bar.dart'; class StartLearnPracticeScreen extends ViewModelWidget { const StartLearnPracticeScreen({super.key}); + void _start(LearnPracticeViewModel viewModel) { + viewModel.playQuestionAudio(); + } + @override Widget build(BuildContext context, LearnPracticeViewModel viewModel) => _buildScaffoldWrapper(viewModel); @@ -47,8 +51,8 @@ class StartLearnPracticeScreen extends ViewModelWidget { ); Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( - onTap: viewModel.goBack, showBackButton: true, + onTap: viewModel.goBack, title: 'Practice Speaking', ); @@ -56,9 +60,10 @@ class StartLearnPracticeScreen extends ViewModelWidget { child: _buildStartButtonContainer(viewModel), ); + Widget _buildStartButtonContainer(LearnPracticeViewModel viewModel) => GestureDetector( - onTap: () => viewModel.goTo(2), + onTap: () => _start(viewModel), child: _buildStartButton(), ); @@ -129,7 +134,7 @@ class StartLearnPracticeScreen extends ViewModelWidget { ]; Widget _buildActionLabel() => Text( - 'Tap the microphone to speak', + 'Tap the start button to listen', style: style14DG400, textAlign: TextAlign.center, ); @@ -149,27 +154,28 @@ class StartLearnPracticeScreen extends ViewModelWidget { Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); Widget _buildReplyButton() => const CustomColumnButton( - icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); + icon: Icons.replay, label: 'Reply', color: kcLightGrey); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) => Expanded(child: _buildMicButton(viewModel)); Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton( - onPressed: () => viewModel.goTo(2), - style: const ButtonStyle( - shape: WidgetStatePropertyAll(CircleBorder()), - padding: WidgetStatePropertyAll(EdgeInsets.all(15)), - shadowColor: WidgetStatePropertyAll(kcPrimaryColor), - backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), - ), - child: _buildMicIcon(), - ); + onPressed: null, + style: const ButtonStyle( + shape: WidgetStatePropertyAll(CircleBorder()), + padding: WidgetStatePropertyAll(EdgeInsets.all(15)), + shadowColor: WidgetStatePropertyAll(kcPrimaryColor), + backgroundColor: WidgetStatePropertyAll(kcVeryLightGrey), + ), + child: _buildMicIcon(), + ); + Widget _buildMicIcon() => const Icon( - Icons.mic, - size: 35, - color: kcWhite, - ); + Icons.mic, + size: 35, + color: kcWhite, + ); Widget _buildEmptySpace() => Expanded(child: Container()); } diff --git a/lib/ui/views/login/login_view.dart b/lib/ui/views/login/login_view.dart index 0689f54..4eacf0e 100644 --- a/lib/ui/views/login/login_view.dart +++ b/lib/ui/views/login/login_view.dart @@ -44,13 +44,13 @@ class LoginView extends StackedView with $LoginView { _buildLoginScreensWrapper(viewModel); Widget _buildLoginScreensWrapper(LoginViewModel viewModel) => PopScope( - canPop: viewModel.currentIndex == 0 ? true : false, + canPop: viewModel.currentPage == 0 ? true : false, onPopInvokedWithResult: (value, data) => WidgetsBinding.instance .addPostFrameCallback((_) => viewModel.goBack()), child: _buildBody(viewModel)); Widget _buildBody(LoginViewModel viewModel) => - IndexedStack(index: viewModel.currentIndex, children: _buildScreens()); + IndexedStack(index: viewModel.currentPage, children: _buildScreens()); List _buildScreens() => [ _buildLoginWithEmailScreen(), diff --git a/lib/ui/views/login/login_viewmodel.dart b/lib/ui/views/login/login_viewmodel.dart index 6b361e8..c2df529 100644 --- a/lib/ui/views/login/login_viewmodel.dart +++ b/lib/ui/views/login/login_viewmodel.dart @@ -37,9 +37,9 @@ class LoginViewModel extends ReactiveViewModel GoogleSignInAccount? get googleUser => _googleUser; // In-app navigation - int _currentIndex = 0; + int _currentPage = 0; - int get currentIndex => _currentIndex; + int get currentPage => _currentPage; // Email bool _focusEmail = false; @@ -139,16 +139,16 @@ class LoginViewModel extends ReactiveViewModel // In app navigation void goTo(int page) { - _currentIndex = page; + _currentPage = page; rebuildUi(); } void goBack() { - if (_currentIndex == 1) { - _currentIndex = 0; + if (_currentPage == 1) { + _currentPage = 0; rebuildUi(); - } else if (_currentIndex == 2) { - _currentIndex = 1; + } else if (_currentPage == 2) { + _currentPage = 1; rebuildUi(); } else { _navigationService.back(); diff --git a/lib/ui/views/startup/startup_viewmodel.dart b/lib/ui/views/startup/startup_viewmodel.dart index a14d868..69daff5 100644 --- a/lib/ui/views/startup/startup_viewmodel.dart +++ b/lib/ui/views/startup/startup_viewmodel.dart @@ -21,6 +21,7 @@ class StartupViewModel extends BaseViewModel { await _authenticationService.getUser(); await _navigationService.replaceWithHomeView(); } else { + // Removable await _navigationService.replaceWithLoginView(); } } diff --git a/lib/ui/widgets/cancel_learn_practice_sheet.dart b/lib/ui/widgets/cancel_learn_practice_sheet.dart index 054063a..40f033f 100644 --- a/lib/ui/widgets/cancel_learn_practice_sheet.dart +++ b/lib/ui/widgets/cancel_learn_practice_sheet.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart'; import '../common/app_colors.dart'; @@ -6,32 +8,37 @@ import '../common/ui_helpers.dart'; import 'custom_bottom_sheet.dart'; import 'custom_elevated_button.dart'; -class CancelLearnPracticeSheet extends StatelessWidget { - final GestureTapCallback? onTap; +class CancelLearnPracticeSheet extends ViewModelWidget { + final GestureTapCallback? onClose; + final GestureTapCallback? onCancel; + final GestureTapCallback? onContinue; - const CancelLearnPracticeSheet({super.key, this.onTap}); + const CancelLearnPracticeSheet( + {super.key, this.onClose, this.onCancel, this.onContinue}); @override - Widget build(BuildContext context) => _buildSheetWrapper(); + Widget build(BuildContext context, LearnPracticeViewModel viewModel) => + _buildSheetWrapper(viewModel); - Widget _buildSheetWrapper() => CustomBottomSheet( - height: 500, onTap: onTap, child: _buildColumnWrapper()); + Widget _buildSheetWrapper(LearnPracticeViewModel viewModel) => + CustomBottomSheet( + height: 500, onTap: onClose, child: _buildColumnWrapper(viewModel)); - Widget _buildColumnWrapper() => Padding( + Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildColumn(), + child: _buildColumn(viewModel), ); - Widget _buildColumn() => Column( + Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( crossAxisAlignment: CrossAxisAlignment.center, - children: _buildSheetChildren(), + children: _buildSheetChildren(viewModel), ); - List _buildSheetChildren() => [ + List _buildSheetChildren(LearnPracticeViewModel viewModel) => [ verticalSpaceLarge, _buildImage(), verticalSpaceMedium, - _buildMessage(), + _buildMessage(viewModel), _buildSubtitle(), verticalSpaceLarge, _buildContinueButton(), @@ -42,10 +49,10 @@ class CancelLearnPracticeSheet extends StatelessWidget { radius: 45, ); - Widget _buildMessage() => Text.rich( + Widget _buildMessage(LearnPracticeViewModel viewModel) => Text.rich( TextSpan(text: 'You’re almost there,', style: style18DG700, children: [ TextSpan( - text: ' Johnny!', + text: ' ${viewModel.user?.firstName ?? ''}!', style: style18P600, ) ]), @@ -59,7 +66,7 @@ class CancelLearnPracticeSheet extends StatelessWidget { Widget _buildContinueButton() => CustomElevatedButton( height: 55, - onTap: onTap, + onTap: onContinue, borderRadius: 12, foregroundColor: kcWhite, text: 'Continue Practice', @@ -68,7 +75,7 @@ class CancelLearnPracticeSheet extends StatelessWidget { Widget _buildEndButton() => CustomElevatedButton( height: 55, - onTap: onTap, + onTap: onCancel, borderRadius: 12, text: 'End Session', backgroundColor: kcWhite, diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index e336e30..6f856f7 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:iconsax/iconsax.dart'; import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/views/learn_module/learn_module_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; @@ -12,29 +11,20 @@ import 'custom_elevated_button.dart'; class LearnModuleTile extends ViewModelWidget { final String title; + final String topics; final String subtitle; + final String description; final ProgressStatuses status; + final List> practices; - const LearnModuleTile({ - super.key, - required this.title, - required this.status, - required this.subtitle, - }); - - IconData _getIcon() { - if (title.contains('Module 1')) { - return Iconsax.cake; - } else if (title.contains('Module 2')) { - return Icons.all_inbox; - } else if (title.contains('Module 3')) { - return Icons.lightbulb_outline; - } else if (title.contains('Module 4')) { - return Icons.search; - } else { - return Iconsax.pen_add; - } - } + const LearnModuleTile( + {super.key, + required this.title, + required this.topics, + required this.status, + required this.subtitle, + required this.practices, + required this.description}); Future _showSheet( {required BuildContext context, @@ -101,25 +91,19 @@ class LearnModuleTile extends ViewModelWidget { child: _buildIcon(), ); - Widget _buildIcon() => Icon( - _getIcon(), + Widget _buildIcon() => const Icon( + Icons.lightbulb_outline, color: kcPrimaryColor, ); Widget _buildTitle() => Text( title, - style: const TextStyle( - fontSize: 16, - color: kcPrimaryColor, - fontWeight: FontWeight.w600, - ), + style: style16P600, ); Widget _buildContent() => Text( subtitle, - style: const TextStyle( - color: kcDarkGrey, - ), + style: style14DG400, ); List _buildExpansionTileChildren( @@ -141,8 +125,8 @@ class LearnModuleTile extends ViewModelWidget { {required BuildContext context, required LearnModuleViewModel viewModel}) => [ - _buildProgressRow(), - verticalSpaceSmall, + // _buildProgressRow(), + // verticalSpaceSmall, _buildActionButtonWrapper(context: context, viewModel: viewModel) ]; @@ -198,7 +182,12 @@ class LearnModuleTile extends ViewModelWidget { text: 'View Lessons', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, - onTap: () async => await viewModel.navigateToLearnLesson(), + onTap: () async => await viewModel.navigateToLearnLesson( + title: title, + topics: topics, + subtitle: subtitle, + practices: practices, + description: description), ); Widget _buildPracticeButtonWrapper( @@ -218,9 +207,7 @@ class LearnModuleTile extends ViewModelWidget { backgroundColor: kcWhite, borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, - onTap: () async => status == ProgressStatuses.completed - ? await viewModel.navigateToLearnPractice() - : await _showSheet(context: context, viewModel: viewModel), + onTap: () async => await viewModel.navigateToLearnPractice(practices), ); Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet( diff --git a/lib/ui/widgets/learn_practice_answer_card.dart b/lib/ui/widgets/learn_practice_answer_card.dart new file mode 100644 index 0000000..fe60aca --- /dev/null +++ b/lib/ui/widgets/learn_practice_answer_card.dart @@ -0,0 +1,81 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +import '../common/app_colors.dart'; +import '../common/enmus.dart'; +import '../common/ui_helpers.dart'; +import '../views/learn_practice/learn_practice_viewmodel.dart'; + +class LearnPracticeAnswerCard extends ViewModelWidget { + final String title; + final StateObjects object; + + const LearnPracticeAnswerCard( + {super.key, required this.title, required this.object}); + + @override + Widget build(BuildContext context, LearnPracticeViewModel viewModel) => + _buildContainer(viewModel); + + Widget _buildContainer(LearnPracticeViewModel viewModel) => Container( + decoration: BoxDecoration( + color: kcWhite, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + child: _buildRow(viewModel), + ); + + Widget _buildRow(LearnPracticeViewModel viewModel) => Row( + children: [_buildPlayButton(viewModel), _buildColumnWrapper()], + ); + + Widget _buildPlayButton(LearnPracticeViewModel viewModel) => ElevatedButton( + style: const ButtonStyle( + shape: WidgetStatePropertyAll(CircleBorder()), + padding: WidgetStatePropertyAll(EdgeInsets.all(5)), + shadowColor: WidgetStatePropertyAll(kcPrimaryColor), + backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), + ), + onPressed: () async => viewModel.player.state == PlayerState.playing + ? viewModel.busyObject == StateObjects.learnPracticeSample + ? await viewModel.pauseSampleAudio() + : await viewModel.pauseRecordedAudio() + : object == StateObjects.learnPracticeSample + ? await viewModel.playSampleAudio() + : await viewModel.playRecordedAudio(), + child: _buildButtonState(viewModel), + ); + + Widget _buildButtonState(LearnPracticeViewModel viewModel) => + viewModel.busy(object) + ? viewModel.busyObject == object + ? _buildPauseIcon() + : _buildPlayIcon() + : viewModel.busyObject == object && + viewModel.player.state == PlayerState.playing + ? _buildPauseIcon() + : _buildPlayIcon(); + + Widget _buildPlayIcon() => const Icon( + Icons.play_arrow_rounded, + size: 25, + color: kcWhite, + ); + + Widget _buildPauseIcon() => const Icon( + Icons.pause, + size: 25, + color: kcWhite, + ); + + Widget _buildColumnWrapper() => Expanded(child: _buildTitle()); + + Widget _buildTitle() => Text( + title, + maxLines: 1, + softWrap: false, + style: style12RP600, + ); +} diff --git a/lib/ui/widgets/learn_practice_card.dart b/lib/ui/widgets/learn_practice_card.dart new file mode 100644 index 0000000..866bd61 --- /dev/null +++ b/lib/ui/widgets/learn_practice_card.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; + +import '../common/app_colors.dart'; +import '../common/ui_helpers.dart'; +import 'custom_elevated_button.dart'; + +class LearnPracticeCard extends ViewModelWidget { + final int index; + final GestureTapCallback? onTap; + final Map practice; + + const LearnPracticeCard( + {super.key, this.onTap, required this.index, required this.practice}); + + @override + Widget build(BuildContext context, LearnPracticeViewModel viewModel) => + _buildContainer(viewModel); + + Widget _buildContainer(LearnPracticeViewModel viewModel) => Container( + height: 200, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: kcPrimaryColor.withValues(alpha: 0.25), + ), + padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 12), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + children: _buildColumnChildren(viewModel)); + + List _buildColumnChildren(LearnPracticeViewModel viewModel) => [ + verticalSpaceTiny, + _buildTitle(), + verticalSpaceSmall, + _buildStartButtonWrapper(viewModel) + ]; + + Widget _buildTitle() => Text( + 'Practice $index', + style: style18DG700, + ); + + Widget _buildStartButtonWrapper(LearnPracticeViewModel viewModel) => SizedBox( + height: 40, + child: _buildStartButton(viewModel), + ); + + Widget _buildStartButton(LearnPracticeViewModel viewModel) => + CustomElevatedButton( + height: 50, + width: 200, + borderRadius: 8, + text: 'Practice', + foregroundColor: kcWhite, + backgroundColor: kcPrimaryColor, + onTap: () => viewModel.setPractice(practice), + ); +} diff --git a/lib/ui/widgets/learn_practice_result_card.dart b/lib/ui/widgets/learn_practice_result_card.dart new file mode 100644 index 0000000..93f7368 --- /dev/null +++ b/lib/ui/widgets/learn_practice_result_card.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/learn_practice_answer_card.dart'; + + +class LearnPracticeResultCard extends ViewModelWidget { + final Map data; + const LearnPracticeResultCard( + {super.key, required this.data}); + + @override + Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildColumnWrapper(); + + Widget _buildColumnWrapper() => SizedBox( + height: 100, + width: double.maxFinite, + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildColumnChildren(), + ); + + List _buildColumnChildren() => + [_buildQuestion(), verticalSpaceSmall, _buildRow()]; + + Widget _buildQuestion() => Text( + data['question_text'], + style: style14DG400, + ); + + Widget _buildRow() => Row( + children: _buildRowChildren(), + ); + + List _buildRowChildren() => [ + _buildSampleResponseWrapper(), + horizontalSpaceSmall, + _buildActualResponseWrapper() + ]; + + Widget _buildSampleResponseWrapper() => + Expanded(child: _buildSampleResponse()); + + Widget _buildSampleResponse() => + const LearnPracticeAnswerCard(title: 'Sample Answer' ,object: StateObjects.learnPracticeSample,); + + Widget _buildActualResponseWrapper() => + Expanded(child: _buildActualResponse()); + + Widget _buildActualResponse() => + const LearnPracticeAnswerCard(title: 'Your Answer',object: StateObjects.learnPracticeAnswer,); + + + +} diff --git a/lib/ui/widgets/practice_results_wrapper.dart b/lib/ui/widgets/learn_practice_results_wrapper.dart similarity index 54% rename from lib/ui/widgets/practice_results_wrapper.dart rename to lib/ui/widgets/learn_practice_results_wrapper.dart index e42943b..5447abb 100644 --- a/lib/ui/widgets/practice_results_wrapper.dart +++ b/lib/ui/widgets/learn_practice_results_wrapper.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; -import 'package:yimaru_app/ui/widgets/practice_result_card.dart'; +import 'package:yimaru_app/ui/widgets/learn_practice_result_card.dart'; import '../common/app_colors.dart'; -class PracticeResultsWrapper extends StatelessWidget { - final List> data; +class LearnPracticeResultsWrapper extends StatelessWidget { + final Map data; - const PracticeResultsWrapper({super.key, required this.data}); + const LearnPracticeResultsWrapper({super.key, required this.data}); @override Widget build(BuildContext context) => _buildContainer(); @@ -28,7 +28,7 @@ class PracticeResultsWrapper extends StatelessWidget { ); List _buildColumnChildren() => - [_buildTitle(), verticalSpaceSmall, _buildResults()]; + [_buildTitle(), verticalSpaceSmall, if (data.isNotEmpty) _buildResult()]; Widget _buildTitle() => Text( 'Conversation Review', @@ -36,15 +36,5 @@ class PracticeResultsWrapper extends StatelessWidget { textAlign: TextAlign.center, ); - Widget _buildResults() => ListView.separated( - shrinkWrap: true, - itemCount: data.length, - padding: EdgeInsets.zero, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) => _buildResult(index), - separatorBuilder: (context, index) => verticalSpaceSmall, - ); - - Widget _buildResult(int index) => - PracticeResultCard(index: index + 1, data: data[index]); + Widget _buildResult() => LearnPracticeResultCard(data: data); } diff --git a/lib/ui/widgets/learn_sub_level_tile.dart b/lib/ui/widgets/learn_sub_level_tile.dart index 5430cb1..1e06cb1 100644 --- a/lib/ui/widgets/learn_sub_level_tile.dart +++ b/lib/ui/widgets/learn_sub_level_tile.dart @@ -72,11 +72,7 @@ class LearnSubLevelTile extends ViewModelWidget { Widget _buildTitle() => Text( title, - style: const TextStyle( - fontSize: 16, - color: kcPrimaryColor, - fontWeight: FontWeight.w600, - ), + style: style16P600, ); Widget _buildProgressStatus() => const ProgressStatus( @@ -86,9 +82,7 @@ class LearnSubLevelTile extends ViewModelWidget { Widget _buildContent() => Text( subtitle, - style: const TextStyle( - color: kcDarkGrey, - ), + style: style14DG400, ); Widget _buildActionButtonWrapper(LearnLevelViewModel viewModel) => SizedBox( @@ -119,18 +113,17 @@ class LearnSubLevelTile extends ViewModelWidget { ); Widget _buildPracticeButtonWrapper(LearnLevelViewModel viewModel) => Expanded( - child: _buildPracticeButton(viewModel), + child: Container(), ); Widget _buildPracticeButton(LearnLevelViewModel viewModel) => - CustomElevatedButton( + const CustomElevatedButton( height: 15, text: 'Practice', borderRadius: 12, backgroundColor: kcWhite, borderColor: kcPrimaryColor, - foregroundColor: kcPrimaryColor, - onTap: () async => await viewModel.navigateToLearnPractice() - // onTap: () async => await viewModel.navigateToLearnLevel(), + foregroundColor: kcPrimaryColor + // onTap: () async => await viewModel.navigateToLearnPractice() ); } diff --git a/lib/ui/widgets/overall_learn_progress.dart b/lib/ui/widgets/overall_learn_progress.dart index 1a047b2..c85ce5d 100644 --- a/lib/ui/widgets/overall_learn_progress.dart +++ b/lib/ui/widgets/overall_learn_progress.dart @@ -40,25 +40,18 @@ class OverallLearnProgress extends StatelessWidget { List _buildProgressInfoChildren() => [_buildProgressInfo(), _buildProgress()]; - Widget _buildProgressInfo() => const Text( + Widget _buildProgressInfo() => Text( 'Overall Progress', - style: TextStyle( - fontSize: 16, - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), + style: style16DG600, ); - Widget _buildProgress() => const Text( - '35%', - style: TextStyle( - color: kcPrimaryColor, - fontWeight: FontWeight.w600, - ), + Widget _buildProgress() => Text( + '0%', + style: style14P400, ); Widget _buildProgressIndicator() => const CustomLinearProgressIndicator( - progress: 0.75, + progress: 0.0, activeColor: kcPrimaryColor, backgroundColor: kcVeryLightGrey, ); diff --git a/lib/ui/widgets/practice_result_card.dart b/lib/ui/widgets/practice_result_card.dart index 04e82c4..a1f7cd0 100644 --- a/lib/ui/widgets/practice_result_card.dart +++ b/lib/ui/widgets/practice_result_card.dart @@ -1,15 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/custom_response_card.dart'; -class PracticeResultCard extends StatelessWidget { - final int index; +class PracticeResultCard extends ViewModelWidget { final Map data; const PracticeResultCard( - {super.key, required this.index, required this.data}); + {super.key, required this.data}); @override - Widget build(BuildContext context) => _buildColumnWrapper(); + Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildColumnWrapper(); Widget _buildColumnWrapper() => SizedBox( height: 100, @@ -27,7 +28,7 @@ class PracticeResultCard extends StatelessWidget { [_buildQuestion(), verticalSpaceSmall, _buildRow()]; Widget _buildQuestion() => Text( - '$index. ${data['question']}', + data['question_text'], style: style14DG400, ); @@ -45,11 +46,11 @@ class PracticeResultCard extends StatelessWidget { Expanded(child: _buildSampleResponse()); Widget _buildSampleResponse() => - const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54'); + const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54'); Widget _buildActualResponseWrapper() => Expanded(child: _buildActualResponse()); Widget _buildActualResponse() => - const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54'); + const CustomResponseCard(title: 'Sample Answer', subtitle: '0:54'); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 85a2413..f6b0c3a 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,14 +6,22 @@ #include "generated_plugin_registrant.h" +#include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin"); + audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar); 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_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); + g_autoptr(FlPluginRegistrar) record_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); + record_linux_plugin_register_with_registrar(record_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 62e3ed5..f947226 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,8 +3,10 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_linux file_selector_linux flutter_secure_storage_linux + record_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 529412e..791238f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import audioplayers_darwin import battery_plus import connectivity_plus import file_selector_macos @@ -14,11 +15,13 @@ import flutter_local_notifications import flutter_secure_storage_darwin import google_sign_in_ios import package_info_plus +import record_macos import sqflite_darwin import video_player_avfoundation import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) @@ -28,6 +31,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 36edf5c..4bb5c3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.13.0" + audioplayers: + dependency: "direct main" + description: + name: audioplayers + sha256: a72dd459d1a48f61a6fb9c0134dba26597c9236af40639ff0eb70eb4e0baab70 + url: "https://pub.dev" + source: hosted + version: "6.6.0" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.dev" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 + url: "https://pub.dev" + source: hosted + version: "6.4.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.dev" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.dev" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: faa8fa6587f996a6f604433b53af44c57a1407d4fe8dff5766cf63d6875e8de9 + url: "https://pub.dev" + source: hosted + version: "5.2.0" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: bafff2b38b6f6d331887558ba6e0a01c9c208d9dbb3ad0005234db065122a734 + url: "https://pub.dev" + source: hosted + version: "4.3.0" battery_plus: dependency: "direct main" description: @@ -988,10 +1044,10 @@ packages: dependency: transitive description: name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.18" + version: "0.12.19" material_color_utilities: dependency: transitive description: @@ -1296,6 +1352,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record: + dependency: transitive + description: + name: record + sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277 + url: "https://pub.dev" + source: hosted + version: "6.2.0" + record_android: + dependency: transitive + description: + name: record_android + sha256: "94783f08403aed33ffb68797bf0715b0812eb852f3c7985644c945faea462ba1" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + record_ios: + dependency: transitive + description: + name: record_ios + sha256: "8df7c136131bd05efc19256af29b2ba6ccc000ccc2c80d4b6b6d7a8d21a3b5a9" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: c31a35cc158cd666fc6395f7f56fc054f31685571684be6b97670a27649ce5c7 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + record_macos: + dependency: transitive + description: + name: record_macos + sha256: "084902e63fc9c0c224c29203d6c75f0bdf9b6a40536c9d916393c8f4c4256488" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "8a81dbc4e14e1272a285bbfef6c9136d070a47d9b0d1f40aa6193516253ee2f6" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" + url: "https://pub.dev" + source: hosted + version: "1.0.7" rxdart: dependency: transitive description: @@ -1489,10 +1609,10 @@ packages: dependency: transitive description: name: test_api - sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" timezone: dependency: transitive description: @@ -1653,6 +1773,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + waveform_flutter: + dependency: transitive + description: + name: waveform_flutter + sha256: "08c9e98d4cf119428d8b3c083ed42c11c468623eaffdf30420ae38e36662922a" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + waveform_recorder: + dependency: "direct main" + description: + name: waveform_recorder + sha256: "1ca0a19b143d1bdef2adfb3d28f0627c18aee5285235c8cf81a89bf29a0420e1" + url: "https://pub.dev" + source: hosted + version: "1.8.0" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7bdec34..c849ba9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: storage_info: ^1.0.0 flutter_html: ^3.0.0 email_validator: any + audioplayers: ^6.6.0 video_player: ^2.10.1 firebase_core: ^4.4.0 in_app_update: ^4.2.5 @@ -36,6 +37,7 @@ dependencies: stacked_services: ^1.1.0 omni_datetime_picker: any json_serializable: ^6.8.0 + waveform_recorder: ^1.8.0 permission_handler: ^12.0.1 firebase_messaging: ^16.1.1 cached_network_image: ^3.4.1 diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index a39e161..e2ba5ab 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -14,6 +14,8 @@ import 'package:yimaru_app/services/image_downloader_service.dart'; import 'package:yimaru_app/services/notification_service.dart'; import 'package:yimaru_app/services/smart_auth_service.dart'; import 'package:yimaru_app/services/course_service.dart'; +import 'package:yimaru_app/services/audio_player_service.dart'; +import 'package:yimaru_app/services/voice_recorder_service.dart'; // @stacked-import import 'test_helpers.mocks.dart'; @@ -38,6 +40,8 @@ import 'test_helpers.mocks.dart'; MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), + MockSpec(onMissingStub: OnMissingStub.returnDefault), // @stacked-mock-spec ], ) @@ -57,6 +61,8 @@ void registerServices() { getAndRegisterNotificationService(); getAndRegisterSmartAuthService(); getAndRegisterCourseService(); + getAndRegisterAudioPlayerService(); + getAndRegisterVoiceRecorderService(); // @stacked-mock-register } @@ -197,6 +203,20 @@ MockCourseService getAndRegisterCourseService() { locator.registerSingleton(service); return service; } + +MockAudioPlayerService getAndRegisterAudioPlayerService() { + _removeRegistrationIfExists(); + final service = MockAudioPlayerService(); + locator.registerSingleton(service); + return service; +} + +MockVoiceRecorderService getAndRegisterVoiceRecorderService() { + _removeRegistrationIfExists(); + final service = MockVoiceRecorderService(); + locator.registerSingleton(service); + return service; +} // @stacked-mock-create void _removeRegistrationIfExists() { diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index 139be32..fe7192d 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -3,38 +3,41 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i9; +import 'package:audioplayers/audioplayers.dart' as _i4; import 'package:dio/dio.dart' as _i2; -import 'package:firebase_messaging/firebase_messaging.dart' as _i28; -import 'package:flutter/material.dart' as _i6; +import 'package:firebase_messaging/firebase_messaging.dart' as _i29; +import 'package:flutter/material.dart' as _i7; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i5; -import 'package:permission_handler/permission_handler.dart' as _i23; -import 'package:stacked_services/stacked_services.dart' as _i4; -import 'package:yimaru_app/models/assessment.dart' as _i12; -import 'package:yimaru_app/models/course.dart' as _i15; -import 'package:yimaru_app/models/course_category.dart' as _i13; -import 'package:yimaru_app/models/course_detail.dart' as _i31; -import 'package:yimaru_app/models/course_lesson.dart' as _i17; -import 'package:yimaru_app/models/course_progress.dart' as _i16; -import 'package:yimaru_app/models/course_subcategory.dart' as _i14; -import 'package:yimaru_app/models/practice.dart' as _i18; -import 'package:yimaru_app/models/practice_question.dart' as _i19; -import 'package:yimaru_app/models/user_model.dart' as _i10; -import 'package:yimaru_app/services/api_service.dart' as _i11; -import 'package:yimaru_app/services/authentication_service.dart' as _i9; -import 'package:yimaru_app/services/course_service.dart' as _i30; -import 'package:yimaru_app/services/dio_service.dart' as _i20; -import 'package:yimaru_app/services/google_auth_service.dart' as _i25; -import 'package:yimaru_app/services/image_downloader_service.dart' as _i26; -import 'package:yimaru_app/services/image_picker_service.dart' as _i24; -import 'package:yimaru_app/services/notification_service.dart' as _i27; -import 'package:yimaru_app/services/permission_handler_service.dart' as _i22; +import 'package:mockito/src/dummies.dart' as _i6; +import 'package:permission_handler/permission_handler.dart' as _i24; +import 'package:stacked_services/stacked_services.dart' as _i5; +import 'package:yimaru_app/models/assessment.dart' as _i13; +import 'package:yimaru_app/models/course.dart' as _i16; +import 'package:yimaru_app/models/course_category.dart' as _i14; +import 'package:yimaru_app/models/course_detail.dart' as _i32; +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_subcategory.dart' as _i15; +import 'package:yimaru_app/models/practice.dart' as _i19; +import 'package:yimaru_app/models/practice_question.dart' as _i20; +import 'package:yimaru_app/models/user_model.dart' as _i11; +import 'package:yimaru_app/services/api_service.dart' as _i12; +import 'package:yimaru_app/services/audio_player_service.dart' as _i33; +import 'package:yimaru_app/services/authentication_service.dart' as _i10; +import 'package:yimaru_app/services/course_service.dart' as _i31; +import 'package:yimaru_app/services/dio_service.dart' as _i21; +import 'package:yimaru_app/services/google_auth_service.dart' as _i26; +import 'package:yimaru_app/services/image_downloader_service.dart' as _i27; +import 'package:yimaru_app/services/image_picker_service.dart' as _i25; +import 'package:yimaru_app/services/notification_service.dart' as _i28; +import 'package:yimaru_app/services/permission_handler_service.dart' as _i23; import 'package:yimaru_app/services/secure_storage_service.dart' as _i3; -import 'package:yimaru_app/services/smart_auth_service.dart' as _i29; -import 'package:yimaru_app/services/status_checker_service.dart' as _i21; +import 'package:yimaru_app/services/smart_auth_service.dart' as _i30; +import 'package:yimaru_app/services/status_checker_service.dart' as _i22; +import 'package:yimaru_app/services/voice_recorder_service.dart' as _i34; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -70,18 +73,28 @@ class _FakeSecureStorageService_1 extends _i1.SmartFake ); } +class _FakeAudioPlayer_2 extends _i1.SmartFake implements _i4.AudioPlayer { + _FakeAudioPlayer_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [NavigationService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNavigationService extends _i1.Mock implements _i4.NavigationService { +class MockNavigationService extends _i1.Mock implements _i5.NavigationService { @override String get previousRoute => (super.noSuchMethod( Invocation.getter(#previousRoute), - returnValue: _i5.dummyValue( + returnValue: _i6.dummyValue( this, Invocation.getter(#previousRoute), ), - returnValueForMissingStub: _i5.dummyValue( + returnValueForMissingStub: _i6.dummyValue( this, Invocation.getter(#previousRoute), ), @@ -90,25 +103,25 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { @override String get currentRoute => (super.noSuchMethod( Invocation.getter(#currentRoute), - returnValue: _i5.dummyValue( + returnValue: _i6.dummyValue( this, Invocation.getter(#currentRoute), ), - returnValueForMissingStub: _i5.dummyValue( + returnValueForMissingStub: _i6.dummyValue( this, Invocation.getter(#currentRoute), ), ) as String); @override - _i6.GlobalKey<_i6.NavigatorState>? nestedNavigationKey(int? index) => + _i7.GlobalKey<_i7.NavigatorState>? nestedNavigationKey(int? index) => (super.noSuchMethod( Invocation.method( #nestedNavigationKey, [index], ), returnValueForMissingStub: null, - ) as _i6.GlobalKey<_i6.NavigatorState>?); + ) as _i7.GlobalKey<_i7.NavigatorState>?); @override void config({ @@ -117,7 +130,7 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { bool? defaultOpaqueRoute, Duration? defaultDurationTransition, bool? defaultGlobalState, - _i4.Transition? defaultTransitionStyle, + _i5.Transition? defaultTransitionStyle, String? defaultTransition, }) => super.noSuchMethod( @@ -138,18 +151,18 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { ); @override - _i7.Future? navigateWithTransition( - _i6.Widget? page, { + _i8.Future? navigateWithTransition( + _i7.Widget? page, { bool? opaque, String? transition = r'', Duration? duration, bool? popGesture, int? id, - _i6.Curve? curve, + _i7.Curve? curve, bool? fullscreenDialog = false, bool? preventDuplicates = true, - _i4.Transition? transitionClass, - _i4.Transition? transitionStyle, + _i5.Transition? transitionClass, + _i5.Transition? transitionStyle, String? routeName, }) => (super.noSuchMethod( @@ -171,21 +184,21 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? replaceWithTransition( - _i6.Widget? page, { + _i8.Future? replaceWithTransition( + _i7.Widget? page, { bool? opaque, String? transition = r'', Duration? duration, bool? popGesture, int? id, - _i6.Curve? curve, + _i7.Curve? curve, bool? fullscreenDialog = false, bool? preventDuplicates = true, - _i4.Transition? transitionClass, - _i4.Transition? transitionStyle, + _i5.Transition? transitionClass, + _i5.Transition? transitionStyle, String? routeName, }) => (super.noSuchMethod( @@ -207,7 +220,7 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override bool back({ @@ -229,7 +242,7 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { @override void popUntil( - _i6.RoutePredicate? predicate, { + _i7.RoutePredicate? predicate, { int? id, }) => super.noSuchMethod( @@ -251,13 +264,13 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { ); @override - _i7.Future? navigateTo( + _i8.Future? navigateTo( String? routeName, { dynamic arguments, int? id, bool? preventDuplicates = true, Map? parameters, - _i6.RouteTransitionsBuilder? transition, + _i7.RouteTransitionsBuilder? transition, }) => (super.noSuchMethod( Invocation.method( @@ -272,21 +285,21 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? navigateToView( - _i6.Widget? view, { + _i8.Future? navigateToView( + _i7.Widget? view, { dynamic arguments, int? id, bool? opaque, - _i6.Curve? curve, + _i7.Curve? curve, Duration? duration, bool? fullscreenDialog = false, bool? popGesture, bool? preventDuplicates = true, - _i4.Transition? transition, - _i4.Transition? transitionStyle, + _i5.Transition? transition, + _i5.Transition? transitionStyle, }) => (super.noSuchMethod( Invocation.method( @@ -306,16 +319,16 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? replaceWith( + _i8.Future? replaceWith( String? routeName, { dynamic arguments, int? id, bool? preventDuplicates = true, Map? parameters, - _i6.RouteTransitionsBuilder? transition, + _i7.RouteTransitionsBuilder? transition, }) => (super.noSuchMethod( Invocation.method( @@ -330,10 +343,10 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? clearStackAndShow( + _i8.Future? clearStackAndShow( String? routeName, { dynamic arguments, int? id, @@ -350,11 +363,11 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? clearStackAndShowView( - _i6.Widget? view, { + _i8.Future? clearStackAndShowView( + _i7.Widget? view, { dynamic arguments, int? id, }) => @@ -368,10 +381,10 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? clearTillFirstAndShow( + _i8.Future? clearTillFirstAndShow( String? routeName, { dynamic arguments, int? id, @@ -390,11 +403,11 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? clearTillFirstAndShowView( - _i6.Widget? view, { + _i8.Future? clearTillFirstAndShowView( + _i7.Widget? view, { dynamic arguments, int? id, }) => @@ -408,12 +421,12 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); @override - _i7.Future? pushNamedAndRemoveUntil( + _i8.Future? pushNamedAndRemoveUntil( String? routeName, { - _i6.RoutePredicate? predicate, + _i7.RoutePredicate? predicate, dynamic arguments, int? id, }) => @@ -428,16 +441,16 @@ class MockNavigationService extends _i1.Mock implements _i4.NavigationService { }, ), returnValueForMissingStub: null, - ) as _i7.Future?); + ) as _i8.Future?); } /// A class which mocks [BottomSheetService]. /// /// See the documentation for Mockito's code generation for more information. class MockBottomSheetService extends _i1.Mock - implements _i4.BottomSheetService { + implements _i5.BottomSheetService { @override - void setCustomSheetBuilders(Map? builders) => + void setCustomSheetBuilders(Map? builders) => super.noSuchMethod( Invocation.method( #setCustomSheetBuilders, @@ -447,7 +460,7 @@ class MockBottomSheetService extends _i1.Mock ); @override - _i7.Future<_i4.SheetResponse?> showBottomSheet({ + _i8.Future<_i5.SheetResponse?> showBottomSheet({ required String? title, String? description, String? confirmButtonTitle = r'Ok', @@ -480,13 +493,13 @@ class MockBottomSheetService extends _i1.Mock #elevation: elevation, }, ), - returnValue: _i7.Future<_i4.SheetResponse?>.value(), + returnValue: _i8.Future<_i5.SheetResponse?>.value(), returnValueForMissingStub: - _i7.Future<_i4.SheetResponse?>.value(), - ) as _i7.Future<_i4.SheetResponse?>); + _i8.Future<_i5.SheetResponse?>.value(), + ) as _i8.Future<_i5.SheetResponse?>); @override - _i7.Future<_i4.SheetResponse?> showCustomSheet({ + _i8.Future<_i5.SheetResponse?> showCustomSheet({ dynamic variant, String? title, String? description, @@ -499,7 +512,7 @@ class MockBottomSheetService extends _i1.Mock bool? showIconInAdditionalButton = false, String? additionalButtonTitle, bool? takesInput = false, - _i8.Color? barrierColor = const _i8.Color(2315255808), + _i9.Color? barrierColor = const _i9.Color(2315255808), double? elevation = 1.0, bool? barrierDismissible = true, bool? isScrollControlled = false, @@ -543,12 +556,12 @@ class MockBottomSheetService extends _i1.Mock #useRootNavigator: useRootNavigator, }, ), - returnValue: _i7.Future<_i4.SheetResponse?>.value(), - returnValueForMissingStub: _i7.Future<_i4.SheetResponse?>.value(), - ) as _i7.Future<_i4.SheetResponse?>); + returnValue: _i8.Future<_i5.SheetResponse?>.value(), + returnValueForMissingStub: _i8.Future<_i5.SheetResponse?>.value(), + ) as _i8.Future<_i5.SheetResponse?>); @override - void completeSheet(_i4.SheetResponse? response) => + void completeSheet(_i5.SheetResponse? response) => super.noSuchMethod( Invocation.method( #completeSheet, @@ -561,10 +574,10 @@ class MockBottomSheetService extends _i1.Mock /// A class which mocks [DialogService]. /// /// See the documentation for Mockito's code generation for more information. -class MockDialogService extends _i1.Mock implements _i4.DialogService { +class MockDialogService extends _i1.Mock implements _i5.DialogService { @override void registerCustomDialogBuilders( - Map? builders) => + Map? builders) => super.noSuchMethod( Invocation.method( #registerCustomDialogBuilders, @@ -576,10 +589,10 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { @override void registerCustomDialogBuilder({ required dynamic variant, - required _i6.Widget Function( - _i6.BuildContext, - _i4.DialogRequest, - dynamic Function(_i4.DialogResponse), + required _i7.Widget Function( + _i7.BuildContext, + _i5.DialogRequest, + dynamic Function(_i5.DialogResponse), )? builder, }) => super.noSuchMethod( @@ -595,17 +608,17 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { ); @override - _i7.Future<_i4.DialogResponse?> showDialog({ + _i8.Future<_i5.DialogResponse?> showDialog({ String? title, String? description, String? cancelTitle, - _i8.Color? cancelTitleColor, + _i9.Color? cancelTitleColor, String? buttonTitle = r'Ok', - _i8.Color? buttonTitleColor, + _i9.Color? buttonTitleColor, bool? barrierDismissible = false, - _i6.RouteSettings? routeSettings, - _i6.GlobalKey<_i6.NavigatorState>? navigatorKey, - _i4.DialogPlatform? dialogPlatform, + _i7.RouteSettings? routeSettings, + _i7.GlobalKey<_i7.NavigatorState>? navigatorKey, + _i5.DialogPlatform? dialogPlatform, }) => (super.noSuchMethod( Invocation.method( @@ -624,13 +637,13 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { #dialogPlatform: dialogPlatform, }, ), - returnValue: _i7.Future<_i4.DialogResponse?>.value(), + returnValue: _i8.Future<_i5.DialogResponse?>.value(), returnValueForMissingStub: - _i7.Future<_i4.DialogResponse?>.value(), - ) as _i7.Future<_i4.DialogResponse?>); + _i8.Future<_i5.DialogResponse?>.value(), + ) as _i8.Future<_i5.DialogResponse?>); @override - _i7.Future<_i4.DialogResponse?> showCustomDialog({ + _i8.Future<_i5.DialogResponse?> showCustomDialog({ dynamic variant, String? title, String? description, @@ -643,13 +656,13 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { bool? showIconInAdditionalButton = false, String? additionalButtonTitle, bool? takesInput = false, - _i8.Color? barrierColor = const _i8.Color(2315255808), + _i9.Color? barrierColor = const _i9.Color(2315255808), bool? barrierDismissible = false, String? barrierLabel = r'', bool? useSafeArea = true, - _i6.RouteSettings? routeSettings, - _i6.GlobalKey<_i6.NavigatorState>? navigatorKey, - _i6.RouteTransitionsBuilder? transitionBuilder, + _i7.RouteSettings? routeSettings, + _i7.GlobalKey<_i7.NavigatorState>? navigatorKey, + _i7.RouteTransitionsBuilder? transitionBuilder, dynamic customData, R? data, }) => @@ -681,21 +694,21 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { #data: data, }, ), - returnValue: _i7.Future<_i4.DialogResponse?>.value(), - returnValueForMissingStub: _i7.Future<_i4.DialogResponse?>.value(), - ) as _i7.Future<_i4.DialogResponse?>); + returnValue: _i8.Future<_i5.DialogResponse?>.value(), + returnValueForMissingStub: _i8.Future<_i5.DialogResponse?>.value(), + ) as _i8.Future<_i5.DialogResponse?>); @override - _i7.Future<_i4.DialogResponse?> showConfirmationDialog({ + _i8.Future<_i5.DialogResponse?> showConfirmationDialog({ String? title, String? description, String? cancelTitle = r'Cancel', - _i8.Color? cancelTitleColor, + _i9.Color? cancelTitleColor, String? confirmationTitle = r'Ok', - _i8.Color? confirmationTitleColor, + _i9.Color? confirmationTitleColor, bool? barrierDismissible = false, - _i6.RouteSettings? routeSettings, - _i4.DialogPlatform? dialogPlatform, + _i7.RouteSettings? routeSettings, + _i5.DialogPlatform? dialogPlatform, }) => (super.noSuchMethod( Invocation.method( @@ -713,13 +726,13 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { #dialogPlatform: dialogPlatform, }, ), - returnValue: _i7.Future<_i4.DialogResponse?>.value(), + returnValue: _i8.Future<_i5.DialogResponse?>.value(), returnValueForMissingStub: - _i7.Future<_i4.DialogResponse?>.value(), - ) as _i7.Future<_i4.DialogResponse?>); + _i8.Future<_i5.DialogResponse?>.value(), + ) as _i8.Future<_i5.DialogResponse?>); @override - void completeDialog(_i4.DialogResponse? response) => + void completeDialog(_i5.DialogResponse? response) => super.noSuchMethod( Invocation.method( #completeDialog, @@ -733,7 +746,7 @@ class MockDialogService extends _i1.Mock implements _i4.DialogService { /// /// See the documentation for Mockito's code generation for more information. class MockAuthenticationService extends _i1.Mock - implements _i9.AuthenticationService { + implements _i10.AuthenticationService { @override int get listenersCount => (super.noSuchMethod( Invocation.getter(#listenersCount), @@ -742,47 +755,47 @@ class MockAuthenticationService extends _i1.Mock ) as int); @override - _i7.Future userLoggedIn() => (super.noSuchMethod( + _i8.Future userLoggedIn() => (super.noSuchMethod( Invocation.method( #userLoggedIn, [], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future getAccessToken() => (super.noSuchMethod( + _i8.Future getAccessToken() => (super.noSuchMethod( Invocation.method( #getAccessToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getRefreshToken() => (super.noSuchMethod( + _i8.Future getRefreshToken() => (super.noSuchMethod( Invocation.method( #getRefreshToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getUserId() => (super.noSuchMethod( + _i8.Future getUserId() => (super.noSuchMethod( Invocation.method( #getUserId, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future saveTokens({ + _i8.Future saveTokens({ required String? access, required String? refresh, }) => @@ -795,101 +808,101 @@ class MockAuthenticationService extends _i1.Mock #refresh: refresh, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future saveUserCredential(Map? data) => + _i8.Future saveUserCredential(Map? data) => (super.noSuchMethod( Invocation.method( #saveUserCredential, [data], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future saveProfileStatus(bool? value) => (super.noSuchMethod( + _i8.Future saveProfileStatus(bool? value) => (super.noSuchMethod( Invocation.method( #saveProfileStatus, [value], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future saveProfilePicture(String? image) => (super.noSuchMethod( + _i8.Future saveProfilePicture(String? image) => (super.noSuchMethod( Invocation.method( #saveProfilePicture, [image], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future saveUserData(_i10.UserModel? data) => (super.noSuchMethod( + _i8.Future saveUserData(_i11.UserModel? data) => (super.noSuchMethod( Invocation.method( #saveUserData, [data], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future updateUserData(Map? data) => + _i8.Future updateUserData(Map? data) => (super.noSuchMethod( Invocation.method( #updateUserData, [data], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isFirstTimeInstall() => (super.noSuchMethod( + _i8.Future isFirstTimeInstall() => (super.noSuchMethod( Invocation.method( #isFirstTimeInstall, [], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future setFirstTimeInstall(bool? value) => (super.noSuchMethod( + _i8.Future setFirstTimeInstall(bool? value) => (super.noSuchMethod( Invocation.method( #setFirstTimeInstall, [value], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future<_i10.UserModel?> getUser() => (super.noSuchMethod( + _i8.Future<_i11.UserModel?> getUser() => (super.noSuchMethod( Invocation.method( #getUser, [], ), - returnValue: _i7.Future<_i10.UserModel?>.value(), - returnValueForMissingStub: _i7.Future<_i10.UserModel?>.value(), - ) as _i7.Future<_i10.UserModel?>); + returnValue: _i8.Future<_i11.UserModel?>.value(), + returnValueForMissingStub: _i8.Future<_i11.UserModel?>.value(), + ) as _i8.Future<_i11.UserModel?>); @override - _i7.Future logout() => (super.noSuchMethod( + _i8.Future logout() => (super.noSuchMethod( Invocation.method( #logout, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void listenToReactiveValues(List? reactiveValues) => @@ -932,9 +945,9 @@ class MockAuthenticationService extends _i1.Mock /// A class which mocks [ApiService]. /// /// See the documentation for Mockito's code generation for more information. -class MockApiService extends _i1.Mock implements _i11.ApiService { +class MockApiService extends _i1.Mock implements _i12.ApiService { @override - _i7.Future> registerWithEmail( + _i8.Future> registerWithEmail( Map? data) => (super.noSuchMethod( Invocation.method( @@ -942,65 +955,65 @@ class MockApiService extends _i1.Mock implements _i11.ApiService { [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> login(Map? data) => + _i8.Future> login(Map? data) => (super.noSuchMethod( Invocation.method( #login, [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> googleAuth(Map? data) => + _i8.Future> googleAuth(Map? data) => (super.noSuchMethod( Invocation.method( #googleAuth, [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> verifyOtp(Map? data) => + _i8.Future> verifyOtp(Map? data) => (super.noSuchMethod( Invocation.method( #verifyOtp, [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> resendOtp(Map? data) => + _i8.Future> resendOtp(Map? data) => (super.noSuchMethod( Invocation.method( #resendOtp, [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> requestResetCode( + _i8.Future> requestResetCode( Map? data) => (super.noSuchMethod( Invocation.method( @@ -1008,52 +1021,52 @@ class MockApiService extends _i1.Mock implements _i11.ApiService { [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> resetPassword(Map? data) => + _i8.Future> resetPassword(Map? data) => (super.noSuchMethod( Invocation.method( #resetPassword, [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> getProfileStatus(_i10.UserModel? user) => + _i8.Future> getProfileStatus(_i11.UserModel? user) => (super.noSuchMethod( Invocation.method( #getProfileStatus, [user], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> getProfileData(int? userId) => + _i8.Future> getProfileData(int? userId) => (super.noSuchMethod( Invocation.method( #getProfileData, [userId], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> completeProfile( + _i8.Future> completeProfile( Map? data) => (super.noSuchMethod( Invocation.method( @@ -1061,13 +1074,13 @@ class MockApiService extends _i1.Mock implements _i11.ApiService { [data], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> updateProfileImage({ + _i8.Future> updateProfileImage({ required int? userId, required Map? data, }) => @@ -1081,126 +1094,126 @@ class MockApiService extends _i1.Mock implements _i11.ApiService { }, ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> getAssessments() => (super.noSuchMethod( + _i8.Future> getAssessments() => (super.noSuchMethod( Invocation.method( #getAssessments, [], ), returnValue: - _i7.Future>.value(<_i12.Assessment>[]), + _i8.Future>.value(<_i13.Assessment>[]), returnValueForMissingStub: - _i7.Future>.value(<_i12.Assessment>[]), - ) as _i7.Future>); + _i8.Future>.value(<_i13.Assessment>[]), + ) as _i8.Future>); @override - _i7.Future> getCourseCategories() => + _i8.Future> getCourseCategories() => (super.noSuchMethod( Invocation.method( #getCourseCategories, [], ), - returnValue: _i7.Future>.value( - <_i13.CourseCategory>[]), - returnValueForMissingStub: _i7.Future>.value( - <_i13.CourseCategory>[]), - ) as _i7.Future>); + returnValue: _i8.Future>.value( + <_i14.CourseCategory>[]), + returnValueForMissingStub: _i8.Future>.value( + <_i14.CourseCategory>[]), + ) as _i8.Future>); @override - _i7.Future> getCourseSubcategories(int? id) => + _i8.Future> getCourseSubcategories(int? id) => (super.noSuchMethod( Invocation.method( #getCourseSubcategories, [id], ), - returnValue: _i7.Future>.value( - <_i14.CourseSubcategory>[]), + returnValue: _i8.Future>.value( + <_i15.CourseSubcategory>[]), returnValueForMissingStub: - _i7.Future>.value( - <_i14.CourseSubcategory>[]), - ) as _i7.Future>); + _i8.Future>.value( + <_i15.CourseSubcategory>[]), + ) as _i8.Future>); @override - _i7.Future> getCourses(int? id) => (super.noSuchMethod( + _i8.Future> getCourses(int? id) => (super.noSuchMethod( Invocation.method( #getCourses, [id], ), - returnValue: _i7.Future>.value(<_i15.Course>[]), + returnValue: _i8.Future>.value(<_i16.Course>[]), returnValueForMissingStub: - _i7.Future>.value(<_i15.Course>[]), - ) as _i7.Future>); + _i8.Future>.value(<_i16.Course>[]), + ) as _i8.Future>); @override - _i7.Future> getCourseProgress(int? id) => + _i8.Future> getCourseProgress(int? id) => (super.noSuchMethod( Invocation.method( #getCourseProgress, [id], ), - returnValue: _i7.Future>.value( - <_i16.CourseProgress>[]), - returnValueForMissingStub: _i7.Future>.value( - <_i16.CourseProgress>[]), - ) as _i7.Future>); + returnValue: _i8.Future>.value( + <_i17.CourseProgress>[]), + returnValueForMissingStub: _i8.Future>.value( + <_i17.CourseProgress>[]), + ) as _i8.Future>); @override - _i7.Future> getCourseLessons(int? id) => + _i8.Future> getCourseLessons(int? id) => (super.noSuchMethod( Invocation.method( #getCourseLessons, [id], ), returnValue: - _i7.Future>.value(<_i17.CourseLesson>[]), + _i8.Future>.value(<_i18.CourseLesson>[]), returnValueForMissingStub: - _i7.Future>.value(<_i17.CourseLesson>[]), - ) as _i7.Future>); + _i8.Future>.value(<_i18.CourseLesson>[]), + ) as _i8.Future>); @override - _i7.Future> completeLesson(int? id) => + _i8.Future> completeLesson(int? id) => (super.noSuchMethod( Invocation.method( #completeLesson, [id], ), returnValue: - _i7.Future>.value({}), + _i8.Future>.value({}), returnValueForMissingStub: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future> getCoursePractices( + _i8.Future> getCoursePractices( Map? data) => (super.noSuchMethod( Invocation.method( #getCoursePractices, [data], ), - returnValue: _i7.Future>.value(<_i18.Practice>[]), + returnValue: _i8.Future>.value(<_i19.Practice>[]), returnValueForMissingStub: - _i7.Future>.value(<_i18.Practice>[]), - ) as _i7.Future>); + _i8.Future>.value(<_i19.Practice>[]), + ) as _i8.Future>); @override - _i7.Future> getCoursePracticeQuestions(int? id) => + _i8.Future> getCoursePracticeQuestions(int? id) => (super.noSuchMethod( Invocation.method( #getCoursePracticeQuestions, [id], ), - returnValue: _i7.Future>.value( - <_i19.PracticeQuestion>[]), + returnValue: _i8.Future>.value( + <_i20.PracticeQuestion>[]), returnValueForMissingStub: - _i7.Future>.value( - <_i19.PracticeQuestion>[]), - ) as _i7.Future>); + _i8.Future>.value( + <_i20.PracticeQuestion>[]), + ) as _i8.Future>); } /// A class which mocks [SecureStorageService]. @@ -1209,47 +1222,47 @@ class MockApiService extends _i1.Mock implements _i11.ApiService { class MockSecureStorageService extends _i1.Mock implements _i3.SecureStorageService { @override - _i7.Future clear() => (super.noSuchMethod( + _i8.Future clear() => (super.noSuchMethod( Invocation.method( #clear, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getBool(String? key) => (super.noSuchMethod( + _i8.Future getBool(String? key) => (super.noSuchMethod( Invocation.method( #getBool, [key], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getString(String? key) => (super.noSuchMethod( + _i8.Future getString(String? key) => (super.noSuchMethod( Invocation.method( #getString, [key], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getInt(String? key) => (super.noSuchMethod( + _i8.Future getInt(String? key) => (super.noSuchMethod( Invocation.method( #getInt, [key], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setString( + _i8.Future setString( String? key, String? value, ) => @@ -1261,12 +1274,12 @@ class MockSecureStorageService extends _i1.Mock value, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setInt( + _i8.Future setInt( String? key, int? value, ) => @@ -1278,12 +1291,12 @@ class MockSecureStorageService extends _i1.Mock value, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setBool( + _i8.Future setBool( String? key, bool? value, ) => @@ -1295,15 +1308,15 @@ class MockSecureStorageService extends _i1.Mock value, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [DioService]. /// /// See the documentation for Mockito's code generation for more information. -class MockDioService extends _i1.Mock implements _i20.DioService { +class MockDioService extends _i1.Mock implements _i21.DioService { @override _i2.Dio get dio => (super.noSuchMethod( Invocation.getter(#dio), @@ -1322,7 +1335,7 @@ class MockDioService extends _i1.Mock implements _i20.DioService { /// /// See the documentation for Mockito's code generation for more information. class MockStatusCheckerService extends _i1.Mock - implements _i21.StatusCheckerService { + implements _i22.StatusCheckerService { @override _i3.SecureStorageService get storage => (super.noSuchMethod( Invocation.getter(#storage), @@ -1344,109 +1357,109 @@ class MockStatusCheckerService extends _i1.Mock ) as bool); @override - _i7.Future getBatteryLevel() => (super.noSuchMethod( + _i8.Future getBatteryLevel() => (super.noSuchMethod( Invocation.method( #getBatteryLevel, [], ), - returnValue: _i7.Future.value(0), - returnValueForMissingStub: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future checkConnection() => (super.noSuchMethod( + _i8.Future checkConnection() => (super.noSuchMethod( Invocation.method( #checkConnection, [], ), - returnValue: _i7.Future.value(false), - returnValueForMissingStub: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + returnValueForMissingStub: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future getAvailableStorage() => (super.noSuchMethod( + _i8.Future getAvailableStorage() => (super.noSuchMethod( Invocation.method( #getAvailableStorage, [], ), - returnValue: _i7.Future.value(0), - returnValueForMissingStub: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + returnValueForMissingStub: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future checkAndUpdate() => (super.noSuchMethod( + _i8.Future checkAndUpdate() => (super.noSuchMethod( Invocation.method( #checkAndUpdate, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [PermissionHandlerService]. /// /// See the documentation for Mockito's code generation for more information. class MockPermissionHandlerService extends _i1.Mock - implements _i22.PermissionHandlerService { + implements _i23.PermissionHandlerService { @override - _i7.Future<_i23.PermissionStatus> requestPermission( - _i23.Permission? requestedPermission) => + _i8.Future<_i24.PermissionStatus> requestPermission( + _i24.Permission? requestedPermission) => (super.noSuchMethod( Invocation.method( #requestPermission, [requestedPermission], ), - returnValue: _i7.Future<_i23.PermissionStatus>.value( - _i23.PermissionStatus.denied), - returnValueForMissingStub: _i7.Future<_i23.PermissionStatus>.value( - _i23.PermissionStatus.denied), - ) as _i7.Future<_i23.PermissionStatus>); + returnValue: _i8.Future<_i24.PermissionStatus>.value( + _i24.PermissionStatus.denied), + returnValueForMissingStub: _i8.Future<_i24.PermissionStatus>.value( + _i24.PermissionStatus.denied), + ) as _i8.Future<_i24.PermissionStatus>); @override - _i7.Future<_i23.PermissionStatus> request(_i23.Permission? permission) => + _i8.Future<_i24.PermissionStatus> request(_i24.Permission? permission) => (super.noSuchMethod( Invocation.method( #request, [permission], ), - returnValue: _i7.Future<_i23.PermissionStatus>.value( - _i23.PermissionStatus.denied), - returnValueForMissingStub: _i7.Future<_i23.PermissionStatus>.value( - _i23.PermissionStatus.denied), - ) as _i7.Future<_i23.PermissionStatus>); + returnValue: _i8.Future<_i24.PermissionStatus>.value( + _i24.PermissionStatus.denied), + returnValueForMissingStub: _i8.Future<_i24.PermissionStatus>.value( + _i24.PermissionStatus.denied), + ) as _i8.Future<_i24.PermissionStatus>); } /// A class which mocks [ImagePickerService]. /// /// See the documentation for Mockito's code generation for more information. class MockImagePickerService extends _i1.Mock - implements _i24.ImagePickerService { + implements _i25.ImagePickerService { @override - _i7.Future gallery() => (super.noSuchMethod( + _i8.Future gallery() => (super.noSuchMethod( Invocation.method( #gallery, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future camera() => (super.noSuchMethod( + _i8.Future camera() => (super.noSuchMethod( Invocation.method( #camera, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [GoogleAuthService]. /// /// See the documentation for Mockito's code generation for more information. -class MockGoogleAuthService extends _i1.Mock implements _i25.GoogleAuthService { +class MockGoogleAuthService extends _i1.Mock implements _i26.GoogleAuthService { @override int get listenersCount => (super.noSuchMethod( Invocation.getter(#listenersCount), @@ -1455,24 +1468,24 @@ class MockGoogleAuthService extends _i1.Mock implements _i25.GoogleAuthService { ) as int); @override - _i7.Future logout() => (super.noSuchMethod( + _i8.Future logout() => (super.noSuchMethod( Invocation.method( #logout, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future googleAuth() => (super.noSuchMethod( + _i8.Future googleAuth() => (super.noSuchMethod( Invocation.method( #googleAuth, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void listenToReactiveValues(List? reactiveValues) => @@ -1516,14 +1529,14 @@ class MockGoogleAuthService extends _i1.Mock implements _i25.GoogleAuthService { /// /// See the documentation for Mockito's code generation for more information. class MockImageDownloaderService extends _i1.Mock - implements _i26.ImageDownloaderService { + implements _i27.ImageDownloaderService { @override - _i7.Future downloader(String? networkImage) => (super.noSuchMethod( + _i8.Future downloader(String? networkImage) => (super.noSuchMethod( Invocation.method( #downloader, [networkImage], ), - returnValue: _i7.Future.value(_i5.dummyValue( + returnValue: _i8.Future.value(_i6.dummyValue( this, Invocation.method( #downloader, @@ -1531,77 +1544,77 @@ class MockImageDownloaderService extends _i1.Mock ), )), returnValueForMissingStub: - _i7.Future.value(_i5.dummyValue( + _i8.Future.value(_i6.dummyValue( this, Invocation.method( #downloader, [networkImage], ), )), - ) as _i7.Future); + ) as _i8.Future); } /// A class which mocks [NotificationService]. /// /// See the documentation for Mockito's code generation for more information. class MockNotificationService extends _i1.Mock - implements _i27.NotificationService { + implements _i28.NotificationService { @override - _i7.Future initialize() => (super.noSuchMethod( + _i8.Future initialize() => (super.noSuchMethod( Invocation.method( #initialize, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setupFlutterNotifications() => (super.noSuchMethod( + _i8.Future setupFlutterNotifications() => (super.noSuchMethod( Invocation.method( #setupFlutterNotifications, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future showNotification(_i28.RemoteMessage? message) => + _i8.Future showNotification(_i29.RemoteMessage? message) => (super.noSuchMethod( Invocation.method( #showNotification, [message], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future subscribeToTopic(String? topic) => (super.noSuchMethod( + _i8.Future subscribeToTopic(String? topic) => (super.noSuchMethod( Invocation.method( #subscribeToTopic, [topic], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future updateFCMToken() => (super.noSuchMethod( + _i8.Future updateFCMToken() => (super.noSuchMethod( Invocation.method( #updateFCMToken, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [SmartAuthService]. /// /// See the documentation for Mockito's code generation for more information. -class MockSmartAuthService extends _i1.Mock implements _i29.SmartAuthService { +class MockSmartAuthService extends _i1.Mock implements _i30.SmartAuthService { @override bool get listenForMultipleSms => (super.noSuchMethod( Invocation.getter(#listenForMultipleSms), @@ -1610,40 +1623,160 @@ class MockSmartAuthService extends _i1.Mock implements _i29.SmartAuthService { ) as bool); @override - _i7.Future dispose() => (super.noSuchMethod( + _i8.Future dispose() => (super.noSuchMethod( Invocation.method( #dispose, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future getSmsCode() => (super.noSuchMethod( + _i8.Future getSmsCode() => (super.noSuchMethod( Invocation.method( #getSmsCode, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); } /// A class which mocks [CourseService]. /// /// See the documentation for Mockito's code generation for more information. -class MockCourseService extends _i1.Mock implements _i30.CourseService { +class MockCourseService extends _i1.Mock implements _i31.CourseService { @override - _i7.Future> getCoursesDetail(int? id) => + _i8.Future> getCoursesDetail(int? id) => (super.noSuchMethod( Invocation.method( #getCoursesDetail, [id], ), returnValue: - _i7.Future>.value(<_i31.CourseDetail>[]), + _i8.Future>.value(<_i32.CourseDetail>[]), returnValueForMissingStub: - _i7.Future>.value(<_i31.CourseDetail>[]), - ) as _i7.Future>); + _i8.Future>.value(<_i32.CourseDetail>[]), + ) as _i8.Future>); } + +/// A class which mocks [AudioPlayerService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAudioPlayerService extends _i1.Mock + implements _i33.AudioPlayerService { + @override + _i4.AudioPlayer get player => (super.noSuchMethod( + Invocation.getter(#player), + returnValue: _FakeAudioPlayer_2( + this, + Invocation.getter(#player), + ), + returnValueForMissingStub: _FakeAudioPlayer_2( + this, + Invocation.getter(#player), + ), + ) as _i4.AudioPlayer); + + @override + _i8.Stream get positionStream => (super.noSuchMethod( + Invocation.getter(#positionStream), + returnValue: _i8.Stream.empty(), + returnValueForMissingStub: _i8.Stream.empty(), + ) as _i8.Stream); + + @override + _i8.Stream get durationStream => (super.noSuchMethod( + Invocation.getter(#durationStream), + returnValue: _i8.Stream.empty(), + returnValueForMissingStub: _i8.Stream.empty(), + ) as _i8.Stream); + + @override + _i8.Stream<_i4.PlayerState> get stateStream => (super.noSuchMethod( + Invocation.getter(#stateStream), + returnValue: _i8.Stream<_i4.PlayerState>.empty(), + returnValueForMissingStub: _i8.Stream<_i4.PlayerState>.empty(), + ) as _i8.Stream<_i4.PlayerState>); + + @override + int get listenersCount => (super.noSuchMethod( + Invocation.getter(#listenersCount), + returnValue: 0, + returnValueForMissingStub: 0, + ) as int); + + @override + _i8.Future playUrl(String? url) => (super.noSuchMethod( + Invocation.method( + #play, + [url], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future pause() => (super.noSuchMethod( + Invocation.method( + #pause, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + _i8.Future seek(Duration? position) => (super.noSuchMethod( + Invocation.method( + #seek, + [position], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + + @override + void listenToReactiveValues(List? reactiveValues) => + super.noSuchMethod( + Invocation.method( + #listenToReactiveValues, + [reactiveValues], + ), + returnValueForMissingStub: null, + ); + + @override + void addListener(void Function()? listener) => super.noSuchMethod( + Invocation.method( + #addListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void removeListener(void Function()? listener) => super.noSuchMethod( + Invocation.method( + #removeListener, + [listener], + ), + returnValueForMissingStub: null, + ); + + @override + void notifyListeners() => super.noSuchMethod( + Invocation.method( + #notifyListeners, + [], + ), + returnValueForMissingStub: null, + ); +} + +/// A class which mocks [VoiceRecorderService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockVoiceRecorderService extends _i1.Mock + implements _i34.VoiceRecorderService {} diff --git a/test/services/audio_player_service_test.dart b/test/services/audio_player_service_test.dart new file mode 100644 index 0000000..a089b0c --- /dev/null +++ b/test/services/audio_player_service_test.dart @@ -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('AudioPlayerServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/services/voice_recorder_service_test.dart b/test/services/voice_recorder_service_test.dart new file mode 100644 index 0000000..01baa0a --- /dev/null +++ b/test/services/voice_recorder_service_test.dart @@ -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('VoiceRecorderServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4294ac5..99cf51a 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,14 +6,18 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AudioplayersWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); BatteryPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("BatteryPlusWindowsPlugin")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( @@ -26,4 +30,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + RecordWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 9c76e24..ba56a87 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,12 +3,14 @@ # list(APPEND FLUTTER_PLUGIN_LIST + audioplayers_windows battery_plus connectivity_plus file_selector_windows firebase_core flutter_secure_storage_windows permission_handler_windows + record_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST