diff --git a/assets/images/profile.png b/assets/images/profile.png index 7d74c01..156251c 100644 Binary files a/assets/images/profile.png and b/assets/images/profile.png differ diff --git a/lib/app/app.dart b/lib/app/app.dart index df4c512..50af9db 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -36,6 +36,8 @@ import 'package:yimaru_app/services/image_picker_service.dart'; import 'package:yimaru_app/services/google_auth_service.dart'; import 'package:yimaru_app/services/image_downloader_service.dart'; import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'; +import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'; // @stacked-import @StackedApp( @@ -65,6 +67,8 @@ import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart'; MaterialRoute(page: LearnLessonView), MaterialRoute(page: FailureView), MaterialRoute(page: ForgetPasswordView), + MaterialRoute(page: LearnLessonDetailView), + MaterialRoute(page: LearnPracticeView), // @stacked-route ], dependencies: [ diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 3afd53d..a6686e2 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -5,10 +5,10 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter/material.dart' as _i27; +import 'package:flutter/material.dart' as _i29; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i28; +import 'package:stacked_services/stacked_services.dart' as _i30; import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart' as _i10; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23; @@ -23,9 +23,13 @@ import 'package:yimaru_app/ui/views/language/language_view.dart' as _i14; import 'package:yimaru_app/ui/views/learn/learn_view.dart' as _i19; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart' as _i24; +import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart' + as _i27; import 'package:yimaru_app/ui/views/learn_level/learn_level_view.dart' as _i20; import 'package:yimaru_app/ui/views/learn_module/learn_module_view.dart' as _i21; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart' + as _i28; import 'package:yimaru_app/ui/views/login/login_view.dart' as _i18; import 'package:yimaru_app/ui/views/onboarding/onboarding_view.dart' as _i3; import 'package:yimaru_app/ui/views/ongoing_progress/ongoing_progress_view.dart' @@ -96,6 +100,10 @@ class Routes { static const forgetPasswordView = '/forget-password-view'; + static const learnLessonDetailView = '/learn-lesson-detail-view'; + + static const learnPracticeView = '/learn-practice-view'; + static const all = { homeView, onboardingView, @@ -122,6 +130,8 @@ class Routes { learnLessonView, failureView, forgetPasswordView, + learnLessonDetailView, + learnPracticeView, }; } @@ -227,17 +237,25 @@ class StackedRouter extends _i1.RouterBase { Routes.forgetPasswordView, page: _i26.ForgetPasswordView, ), + _i1.RouteDef( + Routes.learnLessonDetailView, + page: _i27.LearnLessonDetailView, + ), + _i1.RouteDef( + Routes.learnPracticeView, + page: _i28.LearnPracticeView, + ), ]; final _pagesMap = { _i2.HomeView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i2.HomeView(), settings: data, ); }, _i3.OnboardingView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i3.OnboardingView(), settings: data, ); @@ -246,147 +264,159 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const StartupViewArguments(), ); - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => _i4.StartupView(key: args.key, label: args.label), settings: data, ); }, _i5.ProfileView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i5.ProfileView(), settings: data, ); }, _i6.ProfileDetailView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i6.ProfileDetailView(), settings: data, ); }, _i7.DownloadsView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i7.DownloadsView(), settings: data, ); }, _i8.ProgressView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i8.ProgressView(), settings: data, ); }, _i9.OngoingProgressView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i9.OngoingProgressView(), settings: data, ); }, _i10.AccountPrivacyView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i10.AccountPrivacyView(), settings: data, ); }, _i11.SupportView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i11.SupportView(), settings: data, ); }, _i12.TelegramSupportView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i12.TelegramSupportView(), settings: data, ); }, _i13.CallSupportView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i13.CallSupportView(), settings: data, ); }, _i14.LanguageView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i14.LanguageView(), settings: data, ); }, _i15.PrivacyPolicyView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i15.PrivacyPolicyView(), settings: data, ); }, _i16.TermsAndConditionsView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i16.TermsAndConditionsView(), settings: data, ); }, _i17.RegisterView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i17.RegisterView(), settings: data, ); }, _i18.LoginView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i18.LoginView(), settings: data, ); }, _i19.LearnView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i19.LearnView(), settings: data, ); }, _i20.LearnLevelView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i20.LearnLevelView(), settings: data, ); }, _i21.LearnModuleView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i21.LearnModuleView(), settings: data, ); }, _i22.WelcomeView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i22.WelcomeView(), settings: data, ); }, _i23.AssessmentView: (data) { final args = data.getArgs(nullOk: false); - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => _i23.AssessmentView(key: args.key, data: args.data), settings: data, ); }, _i24.LearnLessonView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i24.LearnLessonView(), settings: data, ); }, _i25.FailureView: (data) { final args = data.getArgs(nullOk: false); - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => _i25.FailureView(key: args.key, label: args.label), settings: data, ); }, _i26.ForgetPasswordView: (data) { - return _i27.MaterialPageRoute( + return _i29.MaterialPageRoute( builder: (context) => const _i26.ForgetPasswordView(), settings: data, ); }, + _i27.LearnLessonDetailView: (data) { + return _i29.MaterialPageRoute( + builder: (context) => const _i27.LearnLessonDetailView(), + settings: data, + ); + }, + _i28.LearnPracticeView: (data) { + return _i29.MaterialPageRoute( + builder: (context) => const _i28.LearnPracticeView(), + settings: data, + ); + }, }; @override @@ -402,7 +432,7 @@ class StartupViewArguments { this.label = 'Loading', }); - final _i27.Key? key; + final _i29.Key? key; final String label; @@ -429,7 +459,7 @@ class AssessmentViewArguments { required this.data, }); - final _i27.Key? key; + final _i29.Key? key; final Map data; @@ -456,7 +486,7 @@ class FailureViewArguments { required this.label, }); - final _i27.Key? key; + final _i29.Key? key; final String label; @@ -477,7 +507,7 @@ class FailureViewArguments { } } -extension NavigatorStateExtension on _i28.NavigationService { +extension NavigatorStateExtension on _i30.NavigationService { Future navigateToHomeView([ int? routerId, bool preventDuplicates = true, @@ -507,7 +537,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future navigateToStartupView({ - _i27.Key? key, + _i29.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -776,7 +806,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future navigateToAssessmentView({ - _i27.Key? key, + _i29.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -807,7 +837,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future navigateToFailureView({ - _i27.Key? key, + _i29.Key? key, required String label, int? routerId, bool preventDuplicates = true, @@ -837,6 +867,34 @@ extension NavigatorStateExtension on _i28.NavigationService { transition: transition); } + Future navigateToLearnLessonDetailView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.learnLessonDetailView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future navigateToLearnPracticeView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return navigateTo(Routes.learnPracticeView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + Future replaceWithHomeView([ int? routerId, bool preventDuplicates = true, @@ -866,7 +924,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future replaceWithStartupView({ - _i27.Key? key, + _i29.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -1135,7 +1193,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future replaceWithAssessmentView({ - _i27.Key? key, + _i29.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -1166,7 +1224,7 @@ extension NavigatorStateExtension on _i28.NavigationService { } Future replaceWithFailureView({ - _i27.Key? key, + _i29.Key? key, required String label, int? routerId, bool preventDuplicates = true, @@ -1195,4 +1253,32 @@ extension NavigatorStateExtension on _i28.NavigationService { parameters: parameters, transition: transition); } + + Future replaceWithLearnLessonDetailView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.learnLessonDetailView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + + Future replaceWithLearnPracticeView([ + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + ]) async { + return replaceWith(Routes.learnPracticeView, + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } } diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart index 20b6c43..ce6dced 100644 --- a/lib/models/user_model.dart +++ b/lib/models/user_model.dart @@ -12,8 +12,12 @@ class UserModel { final String? country; + final String? occupation; + final bool? userInfoLoaded; + + @JsonKey(name: 'user_id') final int? userId; @@ -51,6 +55,7 @@ class UserModel { this.accessToken, this.refreshToken, this.profilePicture, + this.userInfoLoaded , this.profileCompleted, }); diff --git a/lib/models/user_model.g.dart b/lib/models/user_model.g.dart index ab72b2c..2f20493 100644 --- a/lib/models/user_model.g.dart +++ b/lib/models/user_model.g.dart @@ -20,6 +20,7 @@ UserModel _$UserModelFromJson(Map json) => UserModel( refreshToken: json['refresh_token'] as String?, profilePicture: json['profile_picture_url'] as String?, profileCompleted: json['profile_completed'] as bool?, + userInfoLoaded: json['userInfoLoaded'] as bool? ?? false, ); Map _$UserModelToJson(UserModel instance) => { @@ -28,6 +29,7 @@ Map _$UserModelToJson(UserModel instance) => { 'region': instance.region, 'country': instance.country, 'occupation': instance.occupation, + 'userInfoLoaded': instance.userInfoLoaded, 'user_id': instance.userId, 'last_name': instance.lastName, 'birth_day': instance.birthday, diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index 6037dea..4ad59da 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -11,7 +11,7 @@ class ApiService { final _service = locator(); // Register - Future> register(Map data) async { + Future> registerWithEmail(Map data) async { try { Response response = await _service.dio.post( '$kBaseUrl/$kUserUrl/$kRegisterUrl', @@ -66,10 +66,10 @@ class ApiService { } // Google login - Future> googleLogin(Map data) async { + Future> googleAuth(Map data) async { try { Response response = await _service.dio.post( - '$kBaseUrl/$kGoogleLoginUrl', + '$kBaseUrl/$kGoogleAuthUrl', data: data, ); diff --git a/lib/services/authentication_service.dart b/lib/services/authentication_service.dart index 070c333..b409316 100644 --- a/lib/services/authentication_service.dart +++ b/lib/services/authentication_service.dart @@ -37,24 +37,6 @@ class AuthenticationService with ListenableServiceMixin { await _secureService.setString('refreshToken', refresh); } - Future saveUserName(Map data) async { - await _secureService.setString('firstName', data['firstName']); - _user = UserModel( - email: _user?.email, - gender: _user?.gender, - region: _user?.region, - userId: _user?.userId, - country: _user?.country, - lastName: _user?.lastName, - birthday: _user?.birthday, - occupation: _user?.occupation, - accessToken: _user?.accessToken, - refreshToken: _user?.refreshToken, - profilePicture: _user?.profilePicture, - profileCompleted: _user?.profileCompleted, - firstName: await _secureService.getString('firstName'), - ); - } Future saveUserCredential(Map data) async { await _secureService.setInt('userId', data['userId']); @@ -84,6 +66,7 @@ class AuthenticationService with ListenableServiceMixin { accessToken: _user?.accessToken, refreshToken: _user?.refreshToken, profilePicture: _user?.profilePicture, + userInfoLoaded: _user?.userInfoLoaded ?? false, profileCompleted: await _secureService.getBool('profileCompleted')); notifyListeners(); } @@ -103,6 +86,7 @@ class AuthenticationService with ListenableServiceMixin { accessToken: _user?.accessToken, refreshToken: _user?.refreshToken, profileCompleted: _user?.profileCompleted, + userInfoLoaded: _user?.userInfoLoaded ?? false, profilePicture: await _secureService.getString('profileImage'), ); @@ -111,6 +95,7 @@ class AuthenticationService with ListenableServiceMixin { Future saveUserData( {required String image, required UserModel data}) async { + await _secureService.setBool('userInfoLoaded', true); await _secureService.setBool( 'profileCompleted', data.profileCompleted ?? false); await _secureService.setString('profilePicture', image); @@ -127,6 +112,7 @@ class AuthenticationService with ListenableServiceMixin { email: data.email, gender: data.gender, region: data.region, + userInfoLoaded: true, profilePicture: image, userId: _user?.userId, country: data.country, @@ -190,6 +176,7 @@ class AuthenticationService with ListenableServiceMixin { accessToken: await _secureService.getString('accessToken'), refreshToken: await _secureService.getString('refreshToken'), profilePicture: await _secureService.getString('profileImage'), + userInfoLoaded: await _secureService.getBool('userInfoLoaded'), profileCompleted: await _secureService.getBool('profileCompleted'), ); return _user; diff --git a/lib/services/google_auth_service.dart b/lib/services/google_auth_service.dart index 07ceaf3..33a0e62 100644 --- a/lib/services/google_auth_service.dart +++ b/lib/services/google_auth_service.dart @@ -4,7 +4,7 @@ import 'package:yimaru_app/ui/common/app_constants.dart'; class GoogleAuthService { final GoogleSignIn signIn = GoogleSignIn.instance; - Future googleSignIn() async { + Future googleAuth() async { try { GoogleSignInAccount? googleUser; await signIn.initialize(serverClientId: kServerClientId).then((_) async { diff --git a/lib/ui/common/app_colors.dart b/lib/ui/common/app_colors.dart index f7d0acb..3308842 100644 --- a/lib/ui/common/app_colors.dart +++ b/lib/ui/common/app_colors.dart @@ -5,6 +5,7 @@ const Color kcRed = Color(0xffFF4C4C); const Color kcGreen = Color(0xFF1DE964); const Color kcBackgroundColor = kcWhite; const Color kcWhite = Color(0xFFFFFFFF); +const Color kcViolet = Color(0x336A1B9A); const Color kcIndigo = Color(0xff6A1B9A); const Color kcOrange = Color(0xFFF79400); const Color kcSkyBlue = Color(0xFF28B4CD); @@ -13,7 +14,6 @@ const Color kcMediumGrey = Color(0xFF474A54); const Color kcAquamarine = Color(0xFF1DE9B6); const Color kcTransparent = Colors.transparent; const Color kcPrimaryColor = Color(0xFF9E2891); -const Color kcPrimaryAccent = Color(0xFF6A1B9A); const Color kcVeryLightGrey = Color(0xFFE3E3E3); const Color kcPrimaryColorDark = Color(0xFF300151); const Color kcPrimaryColorLight = Color(0x149E2891); diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 0cf9552..d6438fe 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -23,9 +23,12 @@ String kLoginUrl = 'api/v1/auth/customer-login'; String kProfileStatusUrl = 'is-profile-completed'; -String kGoogleLoginUrl = 'api/v1/auth/google/android'; +String kGoogleAuthUrl = 'api/v1/auth/google/android'; String kAssessmentsUrl = 'api/v1/assessment/questions'; String kServerClientId = '574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com'; + +String kSampleVideoUrl = + 'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4'; diff --git a/lib/ui/common/enmus.dart b/lib/ui/common/enmus.dart index 3fbcf65..80a8eb4 100644 --- a/lib/ui/common/enmus.dart +++ b/lib/ui/common/enmus.dart @@ -14,11 +14,13 @@ enum StateObjects { verifyOtp, resendOtp, profileImage, - registration, profileUpdate, resetPassword, loginWithEmail, loginWithGoogle, + loadLessonVideo, requestResetCode, + registerWithEmail, profileCompletion, + registerWithGoogle, } diff --git a/lib/ui/common/ui_helpers.dart b/lib/ui/common/ui_helpers.dart index 01a96ee..32e43da 100644 --- a/lib/ui/common/ui_helpers.dart +++ b/lib/ui/common/ui_helpers.dart @@ -1,4 +1,5 @@ import 'dart:math'; +import 'package:chewie/chewie.dart'; import 'package:flutter_html/flutter_html.dart'; import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; @@ -183,6 +184,13 @@ TextStyle style18W600 = const TextStyle( fontWeight: FontWeight.w600, ); +TextStyle style25W600 = const TextStyle( + fontSize: 25, + color: kcWhite, + fontWeight: FontWeight.w600, +); + + TextStyle style12R700 = const TextStyle( fontSize: 12, color: Colors.red, @@ -198,7 +206,7 @@ TextStyle style14P600 = const TextStyle( fontWeight: FontWeight.w600, ); -TextStyle style25K600 = const TextStyle( +TextStyle style25P600 = const TextStyle( fontSize: 25, color: kcPrimaryColor, fontWeight: FontWeight.w600, @@ -279,6 +287,12 @@ Map htmlStyle = { ), }; +ChewieProgressColors buildChewieProgressIndicator = ChewieProgressColors( + bufferedColor: kcIndigo, + playedColor: kcPrimaryColor, + backgroundColor: kcBackgroundColor, +); + Widget buildToastDescription(String message) => Text( message, maxLines: 4, diff --git a/lib/ui/views/forget_password/forget_password_view.dart b/lib/ui/views/forget_password/forget_password_view.dart index 3283360..312b9ec 100644 --- a/lib/ui/views/forget_password/forget_password_view.dart +++ b/lib/ui/views/forget_password/forget_password_view.dart @@ -29,10 +29,7 @@ class ForgetPasswordView extends StackedView confirmPasswordController.clear(); } - void _inAppPop(ForgetPasswordViewModel viewModel) { - _clearDataOnNavigation(viewModel); - viewModel.goBack(); - } + void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { if (viewModel.currentPage == 0) { @@ -78,41 +75,11 @@ class ForgetPasswordView extends StackedView canPop: true, onPopInvokedWithResult: (value, data) => _pop(value: value, viewModel: viewModel), - child: _buildScaffoldWrapper(viewModel)); + child: _buildBody(viewModel)); - Widget _buildScaffoldWrapper(ForgetPasswordViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffoldStack(viewModel), - ); - Widget _buildScaffoldStack(ForgetPasswordViewModel viewModel) => - Stack(children: [ - _buildScaffold(viewModel), - _buildRequestResetCodeState(viewModel), - _buildResetPasswordState(viewModel) - ]); - Widget _buildScaffold(ForgetPasswordViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildScaffoldChildren(viewModel), - ); - List _buildScaffoldChildren(ForgetPasswordViewModel viewModel) => - [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; - - Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( - showBackButton: true, - showLanguageSelection: true, - onPop: () => _inAppPop(viewModel), - ); - - Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) => - Expanded(child: _buildBodyWrapper(viewModel)); - - Widget _buildBodyWrapper(ForgetPasswordViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBody(viewModel), - ); Widget _buildBody(ForgetPasswordViewModel viewModel) => IndexedStack(index: viewModel.currentPage, children: _buildScreens()); @@ -130,13 +97,5 @@ class ForgetPasswordView extends StackedView resetCodeController: resetCodeController, confirmPasswordController: confirmPasswordController); - Widget _buildRequestResetCodeState(ForgetPasswordViewModel viewModel) => - viewModel.busy(StateObjects.requestResetCode) - ? const PageLoadingIndicator() - : Container(); - Widget _buildResetPasswordState(ForgetPasswordViewModel viewModel) => - viewModel.busy(StateObjects.resetPassword) - ? const PageLoadingIndicator() - : Container(); } diff --git a/lib/ui/views/forget_password/screens/request_reset_code_screen.dart b/lib/ui/views/forget_password/screens/request_reset_code_screen.dart index 2f614cd..d4b9f09 100644 --- a/lib/ui/views/forget_password/screens/request_reset_code_screen.dart +++ b/lib/ui/views/forget_password/screens/request_reset_code_screen.dart @@ -7,7 +7,9 @@ import 'package:yimaru_app/ui/widgets/login_account.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/option_text_divider.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../forget_password_view.form.dart'; class RequestCodeScreen extends ViewModelWidget { @@ -18,6 +20,23 @@ class RequestCodeScreen extends ViewModelWidget { required this.emailController, }); + + Widget getPadding(context){ + double half = screenHeight(context)/2; + return SizedBox(height: half + 375 - half,); + } + + void _inAppPop(ForgetPasswordViewModel viewModel) { + _clearDataOnNavigation(viewModel); + viewModel.goBack(); + } + + void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { + emailController.clear(); + viewModel.resetRequestResetCodeScreen(); + + } + Future _addUserData(ForgetPasswordViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); @@ -29,24 +48,70 @@ class RequestCodeScreen extends ViewModelWidget { await viewModel.requestResetCode(); } + @override Widget build(BuildContext context, ForgetPasswordViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildBody(ForgetPasswordViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); + Widget _buildScaffoldWrapper( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(context: context, viewModel: viewModel), + ); - List _buildBodyChildren(ForgetPasswordViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)]; + Widget _buildScaffoldStack( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Stack( + children: [ + _buildScaffold(context: context,viewModel: viewModel), + _buildRequestResetCodeState(viewModel), + ], + ); - Widget _buildColumnScroller(ForgetPasswordViewModel viewModel) => + Widget _buildScaffold( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(context: context, viewModel: viewModel), + ); + + List _buildScaffoldChildren( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => + [_buildAppBar(viewModel), _buildExpandedBody(context: context, viewModel: viewModel)]; + + Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( + showBackButton: true, + showLanguageSelection: true, + onPop: () => _inAppPop(viewModel), + ); + + Widget _buildExpandedBody( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => + Expanded(child: _buildColumnScroller(context: context, viewModel: viewModel)); + + Widget _buildColumnScroller( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => SingleChildScrollView( - child: _buildUpperColumn(viewModel), + child: _buildBodyWrapper(context: context, viewModel: viewModel), ); + Widget _buildBodyWrapper( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context, viewModel: viewModel), + ); + + Widget _buildBody( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyChildren(context: context, viewModel: viewModel), + ); + + List _buildBodyChildren( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => + [_buildUpperColumn(viewModel),getPadding(context), _buildContinueButtonWrapper(viewModel)]; + + + Widget _buildUpperColumn(ForgetPasswordViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -117,4 +182,9 @@ class RequestCodeScreen extends ViewModelWidget { ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1), ); + + Widget _buildRequestResetCodeState(ForgetPasswordViewModel viewModel) => + viewModel.busy(StateObjects.requestResetCode) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/views/forget_password/screens/reset_password_screen.dart b/lib/ui/views/forget_password/screens/reset_password_screen.dart index 0eb6051..e5e19ef 100644 --- a/lib/ui/views/forget_password/screens/reset_password_screen.dart +++ b/lib/ui/views/forget_password/screens/reset_password_screen.dart @@ -2,11 +2,14 @@ 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 '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_form_label.dart'; import '../../../widgets/custom_linear_progress_indicator.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/obscure_password.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../../../widgets/validator_list_tile.dart'; import '../forget_password_viewmodel.dart'; import '../forget_password_view.form.dart'; @@ -22,6 +25,20 @@ class ResetPasswordScreen extends ViewModelWidget { required this.passwordController, required this.confirmPasswordController}); + void _inAppPop(ForgetPasswordViewModel viewModel) { + _clearDataOnNavigation(viewModel); + viewModel.goBack(); + } + + void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { + + passwordController.clear(); + resetCodeController.clear(); + confirmPasswordController.clear(); + viewModel.resetResetPasswordScreen(); + + } + Future _reset(ForgetPasswordViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); @@ -36,18 +53,58 @@ class ResetPasswordScreen extends ViewModelWidget { @override Widget build(BuildContext context, ForgetPasswordViewModel viewModel) => - _buildBodyChildren(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildBodyChildren(ForgetPasswordViewModel viewModel) => + Widget _buildScaffoldWrapper( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(context: context, viewModel: viewModel), + ); + + Widget _buildScaffoldStack( {required BuildContext context, + required ForgetPasswordViewModel viewModel}) => Stack( + children: [ + _buildScaffold(viewModel), + _buildResetPasswordState(viewModel), + ], + ); + + Widget _buildScaffold( ForgetPasswordViewModel viewModel) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren( ForgetPasswordViewModel viewModel) => + [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( + showBackButton: true, + showLanguageSelection: true, + onPop: () => _inAppPop(viewModel), + ); + + Widget _buildExpandedBody( ForgetPasswordViewModel viewModel) => + Expanded(child: _buildColumnScroller(viewModel)); + + Widget _buildColumnScroller( ForgetPasswordViewModel viewModel) => SingleChildScrollView( - child: _buildBodyColumn(viewModel), + child: _buildBodyWrapper(viewModel), ); - Widget _buildBodyColumn(ForgetPasswordViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildBodyColumnChildren(viewModel), - ); + Widget _buildBodyWrapper( ForgetPasswordViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(viewModel), + ); + + Widget _buildBody(ForgetPasswordViewModel viewModel) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyColumnChildren(viewModel), + ); + + + + List _buildBodyColumnChildren(ForgetPasswordViewModel viewModel) => [ verticalSpaceMedium, @@ -241,4 +298,9 @@ class ResetPasswordScreen extends ViewModelWidget { ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1), ); + + Widget _buildResetPasswordState(ForgetPasswordViewModel viewModel) => + viewModel.busy(StateObjects.resetPassword) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 7a03dd4..524e616 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -76,21 +76,27 @@ class HomeViewModel extends ReactiveViewModel { Future getProfileData() async => await runBusyFuture(_getProfileData()); Future _getProfileData() async { - if (await _statusChecker.checkConnection()) { - Map response = {}; + print('RESPONSE FOR USER DATA ${_user?.firstName}'); - if (_user?.profileCompleted != null && - (_user?.profileCompleted ?? false)) { - if (await _statusChecker.checkConnection()) { - response = await _apiService.getProfileData(_user?.userId); + if (!(_user?.userInfoLoaded ?? false)) { + print('RESPONSE FOR USER DATA 1'); + if (await _statusChecker.checkConnection()) { + Map response = {}; - if (response['status'] == ResponseStatus.success) { - UserModel user = response['data'] as UserModel; + if (_user?.profileCompleted != null && + (_user?.profileCompleted ?? false)) { + if (await _statusChecker.checkConnection()) { + response = await _apiService.getProfileData(_user?.userId); - String image = - await _imageDownloaderService.downloader(user.profilePicture); + if (response['status'] == ResponseStatus.success) { + UserModel user = response['data'] as UserModel; - await _authenticationService.saveUserData(image: image, data: user); + String image = + await _imageDownloaderService.downloader(user.profilePicture); + + await _authenticationService.saveUserData( + image: image, data: user); + } } } } @@ -117,7 +123,6 @@ class HomeViewModel extends ReactiveViewModel { response = {'data': true, 'status': ResponseStatus.success}; } - if (response['status'] == ResponseStatus.success && !response['data']) { await replaceWithOnboarding(); } else if (response['status'] == ResponseStatus.success && diff --git a/lib/ui/views/learn_lesson/learn_lesson_view.dart b/lib/ui/views/learn_lesson/learn_lesson_view.dart index 7b065b6..3f29bf0 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_view.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_view.dart @@ -117,19 +117,24 @@ class LearnLessonView extends StackedView { itemCount: viewModel.lessons.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => _buildTile( - title: viewModel.lessons[index]['title'], - status: viewModel.lessons[index]['status'], - thumbnail: viewModel.lessons[index]['thumbnail']), + title: viewModel.lessons[index]['title'], + status: viewModel.lessons[index]['status'], + thumbnail: viewModel.lessons[index]['thumbnail'], + onLessonTap: () async => + await viewModel.navigateToLearnLessonDetail(), + ), ); Widget _buildTile({ required String title, required String thumbnail, + GestureTapCallback? onLessonTap, required ProgressStatuses status, }) => LearnLessonTile( title: title, status: status, thumbnail: thumbnail, + onLessonTap: onLessonTap, ); } diff --git a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart index fe4bec1..a467282 100644 --- a/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart +++ b/lib/ui/views/learn_lesson/learn_lesson_viewmodel.dart @@ -1,5 +1,6 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; @@ -8,7 +9,6 @@ class LearnLessonViewModel extends BaseViewModel { final _navigationService = locator(); // Lessons - // Downloads final List> _lessons = [ { 'title': '1.1 Introducing Yourself', @@ -31,4 +31,7 @@ class LearnLessonViewModel extends BaseViewModel { // Navigation void pop() => _navigationService.back(); + + Future navigateToLearnLessonDetail() async => + await _navigationService.navigateToLearnLessonDetailView(); } 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 new file mode 100644 index 0000000..13a75d7 --- /dev/null +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart @@ -0,0 +1,178 @@ +import 'package:chewie/chewie.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_viewmodel.dart'; +import 'package:yimaru_app/ui/widgets/custom_circular_progress_indicator.dart'; +import 'package:yimaru_app/ui/widgets/empty_video_player.dart'; + +import '../../common/app_colors.dart'; +import '../../common/enmus.dart'; +import '../../common/ui_helpers.dart'; +import '../../widgets/custom_elevated_button.dart'; +import '../../widgets/small_app_bar.dart'; +import 'learn_lesson_detail_viewmodel.dart'; + +class LearnLessonDetailView extends StackedView { + const LearnLessonDetailView({Key? key}) : super(key: key); + + + Future _navigate(LearnLessonDetailViewModel viewModel)async{ + await viewModel.pause(); + await viewModel.navigateToLearnPractice(); + } + + + // @override + // void onDispose(LearnLessonDetailViewModel viewModel) { + // print('DISPOSED'); + // viewModel.dispose(); + // super.onDispose(viewModel); + // } + + @override + void onViewModelReady(LearnLessonDetailViewModel viewModel) async { + await viewModel.initializePlayer(); + super.onViewModelReady(viewModel); + } + + @override + LearnLessonDetailViewModel viewModelBuilder(BuildContext context) => + LearnLessonDetailViewModel(); + + @override + Widget builder( + BuildContext context, + LearnLessonDetailViewModel viewModel, + Widget? child, + ) => + _buildScaffoldWrapper(viewModel); + + Widget _buildScaffoldWrapper(LearnLessonDetailViewModel viewModel) => + Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(viewModel), + ); + + Widget _buildScaffold(LearnLessonDetailViewModel viewModel) => + SafeArea(child: _buildColumn(viewModel)); + + Widget _buildColumn(LearnLessonDetailViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBarWrapper(viewModel), + _buildBodyColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBarWrapper(LearnLessonDetailViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildAppBar(viewModel)); + + Widget _buildAppBar(LearnLessonDetailViewModel viewModel) => SmallAppBar( + onTap: viewModel.pop, + ); + + Widget _buildBodyColumnWrapper(LearnLessonDetailViewModel viewModel) => + Expanded( + child: _buildBodyColumn(viewModel), + ); + + Widget _buildBodyColumn(LearnLessonDetailViewModel viewModel) => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyColumnChildren(viewModel), + ); + + List _buildBodyColumnChildren(LearnLessonDetailViewModel viewModel) => + [ + _buildLevelsColumnWrapper(viewModel), + _buildContinueButtonWrapper(viewModel) + ]; + + Widget _buildLevelsColumnWrapper(LearnLessonDetailViewModel viewModel) => + Expanded(child: _buildLevelsColumnScrollView(viewModel)); + + Widget _buildLevelsColumnScrollView(LearnLessonDetailViewModel viewModel) => + SingleChildScrollView( + child: _buildLevelsColumn(viewModel), + ); + + Widget _buildLevelsColumn(LearnLessonDetailViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildLevelsColumnChildren(viewModel), + ); + + List _buildLevelsColumnChildren( + LearnLessonDetailViewModel viewModel) => + [ + verticalSpaceMedium, + _buildTitleWrapper(), + verticalSpaceLarge, + _buildVideoPlayerWrapper(viewModel), + verticalSpaceMedium, + _buildDescriptionWrapper(), + ]; + + Widget _buildTitleWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildTitle(), + ); + + Widget _buildTitle() => Text( + '1.3 Common Greetings', + style: style16DG600, + ); + + Widget _buildVideoPlayerWrapper(LearnLessonDetailViewModel viewModel) => + Container( + height: 200, + color: kcBlack, + width: double.maxFinite, + child: _buildVideoPlayerState(viewModel), + ); + + Widget _buildVideoPlayerState(LearnLessonDetailViewModel viewModel) => + viewModel.chewieController != null && + viewModel.videoPlayerController != null && + !viewModel.busy(StateObjects.loadLessonVideo) + ? _buildVideoPlayer(viewModel) + : _buildEmptyVideoPlayer(); + + Widget _buildVideoPlayer(LearnLessonDetailViewModel viewModel) => + _buildChewiePlayer(viewModel); + + Widget _buildChewiePlayer(LearnLessonDetailViewModel viewModel) => + Chewie(controller: viewModel.chewieController!); + + Widget _buildEmptyVideoPlayer() => const EmptyVideoPlayer(); + + Widget _buildDescriptionWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildDescription(), + ); + + 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.', + style: style14DG600, + ); + + Widget _buildContinueButtonWrapper(LearnLessonDetailViewModel viewModel) => + Padding( + padding: const EdgeInsets.only( + left: 15, + right: 15, + bottom: 50, + ), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) => + CustomElevatedButton( + height: 55, + text: 'Practice', + borderRadius: 12, + foregroundColor: kcWhite, + backgroundColor: kcPrimaryColor, + onTap: ()async => await _navigate(viewModel), + ); +} 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 new file mode 100644 index 0000000..b60be87 --- /dev/null +++ b/lib/ui/views/learn_lesson_detail/learn_lesson_detail_viewmodel.dart @@ -0,0 +1,72 @@ +import 'package:chewie/chewie.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; +import 'package:video_player/video_player.dart'; +import 'package:yimaru_app/app/app.router.dart'; +import 'package:yimaru_app/ui/common/app_colors.dart'; +import 'package:yimaru_app/ui/common/app_constants.dart'; +import 'package:yimaru_app/ui/common/enmus.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; + +import '../../../app/app.locator.dart'; +import '../../../services/status_checker_service.dart'; + +class LearnLessonDetailViewModel extends BaseViewModel { + final _statusChecker = locator(); + + final _navigationService = locator(); + + // Video player config + ChewieController? _chewieController; + + ChewieController? get chewieController => _chewieController; + + VideoPlayerController? _videoPlayerController; + + VideoPlayerController? get videoPlayerController => _videoPlayerController; + + // Video player + Future initializePlayer() async => + await runBusyFuture(_initializePlayer(), + busyObject: StateObjects.loadLessonVideo); + + Future _initializePlayer() async { + _videoPlayerController = + VideoPlayerController.networkUrl(Uri.parse(kSampleVideoUrl)); + + await _videoPlayerController?.initialize(); + + if (_videoPlayerController != null) { + print('Initialized'); + _chewieController = ChewieController( + looping: true, + autoPlay: true, + showOptions: true, + showControls: true, + aspectRatio: 16 / 9, + autoInitialize: true, + allowedScreenSleep: false, + videoPlayerController: _videoPlayerController!, + materialProgressColors: buildChewieProgressIndicator); + } + + // rebuildUi(); + } + + Future pause()async{ + await _chewieController?.pause(); + } + + @override + void dispose() { + _videoPlayerController?.dispose(); + _chewieController?.dispose(); + super.dispose(); + } + + // Navigation + void pop() => _navigationService.back(); + + Future navigateToLearnPractice() async=>await _navigationService.navigateToLearnPracticeView(); + +} diff --git a/lib/ui/views/learn_practice/learn_practice_view.dart b/lib/ui/views/learn_practice/learn_practice_view.dart new file mode 100644 index 0000000..4f3fc90 --- /dev/null +++ b/lib/ui/views/learn_practice/learn_practice_view.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_practice/screens/listen_speaker_screen.dart'; +import 'package:yimaru_app/ui/views/learn_practice/screens/practice_intro_screen.dart'; +import 'package:yimaru_app/ui/views/learn_practice/screens/start_practice_screen.dart'; +import 'package:yimaru_app/ui/widgets/profile_image.dart'; +import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart'; + +import '../../common/app_colors.dart'; +import '../../common/ui_helpers.dart'; +import '../../widgets/custom_elevated_button.dart'; +import '../../widgets/small_app_bar.dart'; +import 'learn_practice_viewmodel.dart'; + +class LearnPracticeView extends StackedView { + const LearnPracticeView({Key? key}) : super(key: key); + + @override + LearnPracticeViewModel viewModelBuilder(BuildContext context) => + LearnPracticeViewModel(); + + @override + Widget builder( + BuildContext context, + LearnPracticeViewModel viewModel, + Widget? child, + ) => + _buildPracticeScreensWrapper(viewModel); + + + + Widget _buildPracticeScreensWrapper(LearnPracticeViewModel viewModel) => PopScope( + canPop: true, + onPopInvokedWithResult: (value, data) { + if (!value) return; + WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack()); + }, + child: _buildScaffoldWrapper(viewModel)); + + Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(viewModel), + ); + + Widget _buildScaffoldStack(LearnPracticeViewModel viewModel) => Stack(children: [ + _buildBody(viewModel), + //_buildLoginWithEmailState(viewModel), + //_buildLoginWithGoogleState(viewModel) + ]); + + + + + Widget _buildBody(LearnPracticeViewModel viewModel) => + IndexedStack( + + index: viewModel.currentIndex, children: _buildScreens()); + + List _buildScreens() => [ + _buildPracticeIntroScreen(), + _buildStartPracticeScreen(), + _buildListenSpeakerScreen() + ]; + + Widget _buildPracticeIntroScreen() => const PracticeIntroScreen(); + + Widget _buildStartPracticeScreen() => const StartPracticeScreen(); + + Widget _buildListenSpeakerScreen() => const ListenSpeakerScreen(); + + +} diff --git a/lib/ui/views/learn_practice/learn_practice_viewmodel.dart b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart new file mode 100644 index 0000000..a435375 --- /dev/null +++ b/lib/ui/views/learn_practice/learn_practice_viewmodel.dart @@ -0,0 +1,34 @@ +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +import '../../../app/app.locator.dart'; + +class LearnPracticeViewModel extends BaseViewModel { + final _navigationService = locator(); + + // In-app navigation + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + // In-app navigation + void goTo(int page) { + + _currentIndex = page; + rebuildUi(); + } + + void goBack() { + if(_currentIndex == 0){ + pop(); + }else{ + _currentIndex--; + rebuildUi(); + } + + } + + // Navigation + void pop() => _navigationService.back(); + +} diff --git a/lib/ui/views/learn_practice/screens/listen_speaker_screen.dart b/lib/ui/views/learn_practice/screens/listen_speaker_screen.dart new file mode 100644 index 0000000..7563726 --- /dev/null +++ b/lib/ui/views/learn_practice/screens/listen_speaker_screen.dart @@ -0,0 +1,254 @@ +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 ListenSpeakerScreen extends ViewModelWidget { + const ListenSpeakerScreen({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), + ], + ); + + Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( + onTap: viewModel.goBack, + title: 'Practice Speaking', + ); + + Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) => + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: _buildSpeakingIndicatorChildren(), + ); + + List _buildSpeakingIndicatorChildren() => + [_buildSpeakerLabel(), verticalSpaceMedium, _buildSpeakingIndicator()]; + + Widget _buildSpeakerLabel() => Text( + 'Daniel is speaking...', + style: style14P400, + textAlign: TextAlign.center, + ); + + Widget _buildSpeakingIndicator() => Container( + height: 200, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + radius: 0.7, + stops: const [ + 0.2, + 0.25, + 0.45, + 0.75, + 1, + ], + center: Alignment.center, + colors: [ + kcPrimaryColor.withOpacity(0.4), + kcPrimaryColor.withOpacity(0.4), + kcPrimaryColor.withOpacity(0.15), + kcPrimaryColor.withOpacity(0.1), + kcPrimaryColor.withOpacity(0.05), + ], + // quarterly spread + ), + ), + child: _buildSpinner(), + ); + + Widget _buildSpinner() => const SpinKitWave( + size: 20, + 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}) => + [ + _buildReplyButtonWrapper(), + _buildMicButtonWrapper(), + _buildCancelButtonWrapper(context: context, viewModel: viewModel) + ]; + + Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); + + Widget _buildReplyButton() => const CustomColumnButton( + icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); + + Widget _buildMicButtonWrapper() => Expanded(child: _buildMicButton()); + + Widget _buildMicButton() => ElevatedButton( + onPressed: () {}, + style: const ButtonStyle( + shape: WidgetStatePropertyAll(CircleBorder()), + padding: WidgetStatePropertyAll(EdgeInsets.all(15)), + shadowColor: WidgetStatePropertyAll(kcPrimaryColor), + backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), + ), + child: _buildMicIcon(), + ); + + Widget _buildMicIcon() => const Icon( + Icons.mic, + 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/practice_intro_screen.dart b/lib/ui/views/learn_practice/screens/practice_intro_screen.dart new file mode 100644 index 0000000..4830da2 --- /dev/null +++ b/lib/ui/views/learn_practice/screens/practice_intro_screen.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart'; +import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/small_app_bar.dart'; +import '../../../widgets/speaking_partner_image.dart'; + +class PracticeIntroScreen extends ViewModelWidget { + const PracticeIntroScreen({super.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: _buildColumnWrapper(viewModel)); + + Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(viewModel), + ); + + Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + _buildBodyColumnWrapper(viewModel), + ], + ); + + Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( + onTap: viewModel.goBack, + title: 'Practice Speaking', + ); + + Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Expanded( + child: _buildBodyColumn(viewModel), + ); + + Widget _buildBodyColumn(LearnPracticeViewModel viewModel) => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyColumnChildren(viewModel), + ); + + List _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [ + _buildPracticeColumnWrapper(viewModel), + _buildContinueButtonWrapper(viewModel) + ]; + + 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.center, + children: _buildPracticeColumnChildren(viewModel), + ); + + List _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => + [ + verticalSpaceMassive, + _buildImage(), + verticalSpaceMedium, + _buildPartnerName(), + verticalSpaceMedium, + _buildTitle(), + verticalSpaceMedium, + _buildSubtitle() + ]; + + Widget _buildImage() => const SpeakingPartnerImage(radius: 75,); + + Widget _buildPartnerName() => Text.rich( + TextSpan(text: 'Daniel', style: style14DG600, children: [ + TextSpan( + text: ' - Your Speaking Partner', + style: style14MG400, + ) + ]), + ); + + Widget _buildTitle() => Text( + 'Let \'s practice what you just learnt!', + style: style25DG600, + textAlign: TextAlign.center, + ); + + Widget _buildSubtitle() => Text( + 'I’ll ask you a few questions, and you can respond naturally.', + style: style14DG400, + textAlign: TextAlign.center, + ); + + Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => + Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildContinueButton(viewModel), + ); + + Widget _buildContinueButton(LearnPracticeViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + text: 'Start Practice', + foregroundColor: kcWhite, + onTap: ()=> viewModel.goTo(1), + backgroundColor: kcPrimaryColor, + ); +} diff --git a/lib/ui/views/learn_practice/screens/start_practice_screen.dart b/lib/ui/views/learn_practice/screens/start_practice_screen.dart new file mode 100644 index 0000000..8f33ad4 --- /dev/null +++ b/lib/ui/views/learn_practice/screens/start_practice_screen.dart @@ -0,0 +1,172 @@ +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/custom_column_button.dart'; + +import '../../../common/app_colors.dart'; +import '../../../common/ui_helpers.dart'; +import '../../../widgets/small_app_bar.dart'; + +class StartPracticeScreen extends ViewModelWidget { + const StartPracticeScreen({super.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: _buildBodyColumnWrapper(viewModel)); + + Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBodyColumn(viewModel), + ); + + Widget _buildBodyColumn(LearnPracticeViewModel viewModel) => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyColumnChildren(viewModel), + ); + + List _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [ + _buildAppBarWrapper(viewModel), + _buildStartButtonWrapper(viewModel), + _buildLowerButtonsSectionWrapper(viewModel) + ]; + + Widget _buildAppBarWrapper(LearnPracticeViewModel viewModel) => Column( + children: [ + verticalSpaceMedium, + _buildAppBar(viewModel), + ], + ); + + Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( + onTap: viewModel.goBack, + title: 'Practice Speaking', + ); + + Widget _buildStartButtonWrapper(LearnPracticeViewModel viewModel) => Expanded( + child: _buildStartButtonContainer(viewModel), + ); + + Widget _buildStartButtonContainer(LearnPracticeViewModel viewModel) => + GestureDetector( + onTap: () => viewModel.goTo(2), + child: _buildStartButton(), + ); + + Widget _buildStartButton() => Container( + width: 150, + height: 150, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: SweepGradient( + stops: const [ + 0.0, + 0.1, + 0.2, + 0.3, + 0.4, + 0.5, + 0.6, + 0.8, + 0.9, + 1, + ], + endAngle: 8, + startAngle: 0.0, + center: Alignment.center, + colors: [ + kcPrimaryColor.withOpacity(0.3), + kcIndigo.withOpacity(0.2), + kcIndigo.withOpacity(0.3), + kcIndigo.withOpacity(0.4), + kcIndigo.withOpacity(0.5), + kcPrimaryColor.withOpacity(0.5), + kcPrimaryColor.withOpacity(0.4), + kcPrimaryColor.withOpacity(0.3), + kcPrimaryColor.withOpacity(0.2), + kcPrimaryColor.withOpacity(0.5), + ], + // quarterly spread + ), + ), + child: _buildStartText(), + ); + + Widget _buildStartText() => Text( + 'Start', + style: style25W600, + ); + + Widget _buildLowerButtonsSectionWrapper(LearnPracticeViewModel viewMode) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: _buildLowerButtonsSection(viewMode), + ); + + Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildLowerButtonsSectionChildren(viewModel), + ); + + List _buildLowerButtonsSectionChildren( + LearnPracticeViewModel viewModel) => + [ + _buildActionLabel(), + verticalSpaceMedium, + _buildButtonsRowWrapper(), + verticalSpaceMedium, + ]; + + Widget _buildActionLabel() => Text( + 'Tap the microphone to speak', + style: style14DG400, + textAlign: TextAlign.center, + ); + + Widget _buildButtonsRowWrapper() => Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildButtonsRowChildren(), + ); + + List _buildButtonsRowChildren() => [ + _buildReplyButtonWrapper(), + _buildMicButtonWrapper(), + _buildEmptySpace() + ]; + + Widget _buildReplyButtonWrapper() => Expanded(child: _buildReplyButton()); + + Widget _buildReplyButton() => const CustomColumnButton( + icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); + + Widget _buildMicButtonWrapper() => Expanded(child: _buildMicButton()); + + Widget _buildMicButton() => ElevatedButton( + onPressed: () {}, + style: const ButtonStyle( + shape: WidgetStatePropertyAll(CircleBorder()), + padding: WidgetStatePropertyAll(EdgeInsets.all(15)), + shadowColor: WidgetStatePropertyAll(kcPrimaryColor), + backgroundColor: WidgetStatePropertyAll(kcPrimaryColor), + ), + child: _buildMicIcon(), + ); + + Widget _buildMicIcon() => const Icon( + 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 da14855..6a44a13 100644 --- a/lib/ui/views/login/login_view.dart +++ b/lib/ui/views/login/login_view.dart @@ -54,38 +54,11 @@ class LoginView extends StackedView with $LoginView { if (!value) return; WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack()); }, - child: _buildScaffoldWrapper(viewModel)); + child: _buildBody(viewModel)); - Widget _buildScaffoldWrapper(LoginViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffoldStack(viewModel), - ); - Widget _buildScaffoldStack(LoginViewModel viewModel) => Stack(children: [ - _buildScaffold(viewModel), - _buildLoginWithEmailState(viewModel), - _buildLoginWithGoogleState(viewModel) - ]); - Widget _buildScaffold(LoginViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildScaffoldChildren(viewModel), - ); - List _buildScaffoldChildren(LoginViewModel viewModel) => - [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; - - Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar( - showBackButton: false, - showLanguageSelection: true, - ); - Widget _buildExpandedBody(LoginViewModel viewModel) => - Expanded(child: _buildBodyWrapper(viewModel)); - - Widget _buildBodyWrapper(LoginViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBody(viewModel), - ); Widget _buildBody(LoginViewModel viewModel) => IndexedStack(index: viewModel.currentIndex, children: _buildScreens()); @@ -106,13 +79,4 @@ class LoginView extends StackedView with $LoginView { otpController: otpController, phoneNumberController: phoneNumberController); - Widget _buildLoginWithEmailState(LoginViewModel viewModel) => - viewModel.busy(StateObjects.loginWithEmail) - ? const PageLoadingIndicator() - : Container(); - - Widget _buildLoginWithGoogleState(LoginViewModel viewModel) => - viewModel.busy(StateObjects.loginWithGoogle) - ? const PageLoadingIndicator() - : Container(); } diff --git a/lib/ui/views/login/login_viewmodel.dart b/lib/ui/views/login/login_viewmodel.dart index 0c971dd..a87550b 100644 --- a/lib/ui/views/login/login_viewmodel.dart +++ b/lib/ui/views/login/login_viewmodel.dart @@ -26,7 +26,7 @@ class LoginViewModel extends FormViewModel { final _authenticationService = locator(); - // Navigation + // In-app navigation int _currentIndex = 0; int get currentIndex => _currentIndex; @@ -171,18 +171,18 @@ class LoginViewModel extends FormViewModel { } } - Future googleLogin() async => await runBusyFuture(_googleLogin(), + Future signInWithGoogle() async => await runBusyFuture(_signInWithGoogle(), busyObject: StateObjects.loginWithGoogle); - Future _googleLogin() async { + Future _signInWithGoogle() async { if (await _statusChecker.checkConnection()) { - GoogleSignInAccount? googleUser = await _googleAuthService.googleSignIn(); + GoogleSignInAccount? googleUser = await _googleAuthService.googleAuth(); Map data = { 'id_token': googleUser?.authentication.idToken ?? '', }; - Map response = await _apiService.googleLogin(data); + Map response = await _apiService.googleAuth(data); if (response['status'] == ResponseStatus.success) { UserModel user = response['data'] as UserModel; diff --git a/lib/ui/views/login/screens/login_otp_screen.dart b/lib/ui/views/login/screens/login_otp_screen.dart index ad80f70..a8fd53d 100644 --- a/lib/ui/views/login/screens/login_otp_screen.dart +++ b/lib/ui/views/login/screens/login_otp_screen.dart @@ -8,6 +8,7 @@ import 'package:yimaru_app/ui/widgets/custom_cursor.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../login_viewmodel.dart'; import '../login_view.form.dart'; @@ -20,29 +21,68 @@ class LoginOtpScreen extends ViewModelWidget { required this.otpController, required this.phoneNumberController}); + Widget getPadding(context){ + double half = screenHeight(context)/2; + return SizedBox(height: half + 325 - half,); + } + @override Widget build(BuildContext context, LoginViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context,viewModel: viewModel); - Widget _buildBody(LoginViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); + Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(context: context,viewModel: viewModel), + ); - List _buildBodyChildren(LoginViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)]; - Widget _buildColumnScroller(LoginViewModel viewModel) => + + Widget _buildScaffold({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(context: context,viewModel: viewModel), + ); + + List _buildScaffoldChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildAppBar(viewModel), _buildExpandedBody(context: context,viewModel: viewModel)]; + + Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody({required BuildContext context,required LoginViewModel viewModel}) => + Expanded(child: _buildColumnScroller(context: context,viewModel: viewModel)); + + Widget _buildColumnScroller({required BuildContext context,required LoginViewModel viewModel}) => SingleChildScrollView( - child: _buildUpperColumn(viewModel), + child: _buildBodyWrapper(context: context,viewModel: viewModel), ); + Widget _buildBodyWrapper({required BuildContext context,required LoginViewModel viewModel}) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context,viewModel: viewModel), + ); + + Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyChildren(context: context,viewModel: viewModel), + ); + + + + + List _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildUpperColumn(viewModel),getPadding(context), _buildContinueButton(viewModel)]; + + + Widget _buildUpperColumn(LoginViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildUpperColumnChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + List _buildUpperColumnChildren(LoginViewModel viewModel) => [ verticalSpaceMedium, diff --git a/lib/ui/views/login/screens/login_with_email_screen.dart b/lib/ui/views/login/screens/login_with_email_screen.dart index 3572948..f9c078f 100644 --- a/lib/ui/views/login/screens/login_with_email_screen.dart +++ b/lib/ui/views/login/screens/login_with_email_screen.dart @@ -4,9 +4,12 @@ import 'package:yimaru_app/ui/views/login/login_view.form.dart'; import 'package:yimaru_app/ui/widgets/obscure_password.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/option_text_divider.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../../../widgets/register_for_account.dart'; import '../login_viewmodel.dart'; @@ -19,6 +22,12 @@ class LoginWithEmailScreen extends ViewModelWidget { required this.emailController, required this.passwordController}); + + Widget getPadding(context){ + double half = screenHeight(context)/2; + return SizedBox(height: half + 25 - half,); + } + Future _login(LoginViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); @@ -33,22 +42,58 @@ class LoginWithEmailScreen extends ViewModelWidget { @override Widget build(BuildContext context, LoginViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context,viewModel: viewModel); - Widget _buildBody(LoginViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); + Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(context: context,viewModel: viewModel), + ); - List _buildBodyChildren(LoginViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildLowerColumn(viewModel)]; + Widget _buildScaffoldStack({required BuildContext context,required LoginViewModel viewModel}) => Stack(children: [ + _buildScaffold(context: context,viewModel: viewModel), + _buildLoginWithEmailState(viewModel), + _buildLoginWithGoogleState(viewModel) + ]); - Widget _buildColumnScroller(LoginViewModel viewModel) => + Widget _buildScaffold({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(context: context,viewModel: viewModel), + ); + + List _buildScaffoldChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildAppBar(viewModel), _buildExpandedBody(context: context,viewModel: viewModel)]; + + Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody({required BuildContext context,required LoginViewModel viewModel}) => + Expanded(child: _buildColumnScroller(context: context,viewModel: viewModel)); + + Widget _buildColumnScroller({required BuildContext context,required LoginViewModel viewModel}) => SingleChildScrollView( - child: _buildUpperColumn(viewModel), + child: _buildBodyWrapper(context: context,viewModel: viewModel), ); + Widget _buildBodyWrapper({required BuildContext context,required LoginViewModel viewModel}) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context,viewModel: viewModel), + ); + + Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildBodyChildren(context: context,viewModel: viewModel), + ); + + + + + List _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildUpperColumn(viewModel),getPadding(context), _buildLowerColumn(viewModel)]; + + + Widget _buildUpperColumn(LoginViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -184,7 +229,7 @@ class LoginWithEmailScreen extends ViewModelWidget { borderColor: kcPrimaryColor, foregroundColor: kcPrimaryColor, leadingImage: 'assets/icons/google.png', - onTap: () async => await viewModel.googleLogin(), + onTap: () async => await viewModel.signInWithGoogle(), ); Widget _buildOptionTextDivider() => const OptionTextDivider(); @@ -200,4 +245,15 @@ class LoginWithEmailScreen extends ViewModelWidget { foregroundColor: kcPrimaryColor, text: 'Login with Phone Number', ); + + + Widget _buildLoginWithEmailState(LoginViewModel viewModel) => + viewModel.busy(StateObjects.loginWithEmail) + ? const PageLoadingIndicator() + : Container(); + + Widget _buildLoginWithGoogleState(LoginViewModel viewModel) => + viewModel.busy(StateObjects.loginWithGoogle) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/views/login/screens/login_with_phone_number_screen.dart b/lib/ui/views/login/screens/login_with_phone_number_screen.dart index 075b90f..29ee135 100644 --- a/lib/ui/views/login/screens/login_with_phone_number_screen.dart +++ b/lib/ui/views/login/screens/login_with_phone_number_screen.dart @@ -8,6 +8,7 @@ import 'package:yimaru_app/ui/widgets/register_for_account.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/phone_number_prefix.dart'; import '../login_view.form.dart'; @@ -17,29 +18,70 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget { const LoginWithPhoneNumberScreen( {super.key, required this.phoneNumberController}); + Widget getPadding(context){ + double half = screenHeight(context)/2; + return SizedBox(height: half + 175 - half,); + } + @override Widget build(BuildContext context, LoginViewModel viewModel) => - _buildBody(viewModel); - Widget _buildBody(LoginViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), - ); + _buildScaffoldWrapper(context: context,viewModel: viewModel); - List _buildBodyChildren(LoginViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildLowerColumn(viewModel)]; - Widget _buildColumnScroller(LoginViewModel viewModel) => + + Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(context: context,viewModel: viewModel), + ); + + + + Widget _buildScaffold({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(context: context,viewModel: viewModel), + ); + + List _buildScaffoldChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildAppBar(viewModel), _buildExpandedBody(context: context,viewModel: viewModel)]; + + Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody({required BuildContext context,required LoginViewModel viewModel}) => + Expanded(child: _buildColumnScroller(context: context,viewModel: viewModel)); + + Widget _buildColumnScroller({required BuildContext context,required LoginViewModel viewModel}) => SingleChildScrollView( - child: _buildUpperColumn(viewModel), + child: _buildBodyWrapper(context: context,viewModel: viewModel), ); + Widget _buildBodyWrapper({required BuildContext context,required LoginViewModel viewModel}) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context,viewModel: viewModel), + ); + + Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildBodyChildren(context: context,viewModel: viewModel), + ); + + + + + List _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => + [_buildUpperColumn(viewModel),getPadding(context), _buildLowerColumn(viewModel)]; + + + Widget _buildUpperColumn(LoginViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildUpperColumnChildren(viewModel), - ); + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildUpperColumnChildren(viewModel), + ); + List _buildUpperColumnChildren(LoginViewModel viewModel) => [ verticalSpaceMedium, diff --git a/lib/ui/views/register/register_view.dart b/lib/ui/views/register/register_view.dart index 6465c6a..d50a053 100644 --- a/lib/ui/views/register/register_view.dart +++ b/lib/ui/views/register/register_view.dart @@ -85,40 +85,9 @@ class RegisterView extends StackedView with $RegisterView { canPop: false, onPopInvokedWithResult: (value, data) => _pop(value: value, viewModel: viewModel), - child: _buildScaffoldWrapper(viewModel)); + child: _buildBody(viewModel)); - Widget _buildScaffoldWrapper(RegisterViewModel viewModel) => Scaffold( - backgroundColor: kcBackgroundColor, - body: _buildScaffoldStack(viewModel), - ); - Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack(children: [ - _buildScaffold(viewModel), - _buildRegistrationState(viewModel), - _buildVerityOtpState(viewModel) - ]); - - Widget _buildScaffold(RegisterViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildScaffoldChildren(viewModel), - ); - - List _buildScaffoldChildren(RegisterViewModel viewModel) => - [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; - - Widget _buildAppBar(RegisterViewModel viewModel) => LargeAppBar( - showBackButton: true, - showLanguageSelection: true, - onPop: () => _inAppPop(viewModel), - ); - - Widget _buildExpandedBody(RegisterViewModel viewModel) => - Expanded(child: _buildBodyWrapper(viewModel)); - - Widget _buildBodyWrapper(RegisterViewModel viewModel) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: _buildBody(viewModel), - ); Widget _buildBody(RegisterViewModel viewModel) => IndexedStack(index: viewModel.currentPage, children: _buildScreens()); @@ -146,13 +115,4 @@ class RegisterView extends StackedView with $RegisterView { passwordController: passwordController, confirmPasswordController: confirmPasswordController); - Widget _buildRegistrationState(RegisterViewModel viewModel) => - viewModel.busy(StateObjects.registration) - ? const PageLoadingIndicator() - : Container(); - - Widget _buildVerityOtpState(RegisterViewModel viewModel) => - viewModel.busy(StateObjects.verifyOtp) - ? const PageLoadingIndicator() - : Container(); } diff --git a/lib/ui/views/register/register_viewmodel.dart b/lib/ui/views/register/register_viewmodel.dart index 72adb44..62a3eb6 100644 --- a/lib/ui/views/register/register_viewmodel.dart +++ b/lib/ui/views/register/register_viewmodel.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:google_sign_in/google_sign_in.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; @@ -10,6 +11,7 @@ import 'package:yimaru_app/ui/views/home/home_view.dart'; import '../../../app/app.locator.dart'; import '../../../models/user_model.dart'; +import '../../../services/google_auth_service.dart'; import '../../../services/status_checker_service.dart'; class RegisterViewModel extends FormViewModel { @@ -19,6 +21,9 @@ class RegisterViewModel extends FormViewModel { final _navigationService = locator(); + final _googleAuthService = locator(); + + final _authenticationService = locator(); // Navigation @@ -284,12 +289,12 @@ class RegisterViewModel extends FormViewModel { // Remote api calls // Register - Future register() async => - await runBusyFuture(_register(), busyObject: StateObjects.registration); + Future registerWithEmail() async => + await runBusyFuture(_register(), busyObject: StateObjects.registerWithEmail); Future _register() async { if (await _statusChecker.checkConnection()) { - Map response = await _apiService.register(_userData); + Map response = await _apiService.registerWithEmail(_userData); if (response['status'] == ResponseStatus.success) { goTo(page: 3); @@ -300,6 +305,38 @@ class RegisterViewModel extends FormViewModel { } } + // Register with google + + Future registerWithGoogle() async => await runBusyFuture(_googleLogin(), + busyObject: StateObjects.registerWithGoogle); + + Future _googleLogin() async { + if (await _statusChecker.checkConnection()) { + GoogleSignInAccount? googleUser = await _googleAuthService.googleAuth(); + + Map data = { + 'id_token': googleUser?.authentication.idToken ?? '', + }; + + Map response = await _apiService.googleAuth(data); + + if (response['status'] == ResponseStatus.success) { + UserModel user = response['data'] as UserModel; + Map data = { + 'userId': user.userId, + 'accessToken': user.accessToken, + 'refreshToken': user.refreshToken + }; + await _authenticationService.saveUserCredential(data); + clearUserData(); + await replaceWithHome(); + showSuccessToast(response['message']); + } else { + showErrorToast(response['message']); + } + } + } + Future verifyOtp() async => await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp); diff --git a/lib/ui/views/register/screens/create_password_screen.dart b/lib/ui/views/register/screens/create_password_screen.dart index 5549882..879c511 100644 --- a/lib/ui/views/register/screens/create_password_screen.dart +++ b/lib/ui/views/register/screens/create_password_screen.dart @@ -7,9 +7,12 @@ import 'package:yimaru_app/ui/widgets/custom_linear_progress_indicator.dart'; import 'package:yimaru_app/ui/widgets/validator_list_tile.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/obscure_password.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../register_view.form.dart'; class CreatePasswordScreen extends ViewModelWidget { @@ -31,21 +34,54 @@ class CreatePasswordScreen extends ViewModelWidget { }; viewModel.addUserData(data); - await viewModel.register(); + await viewModel.registerWithEmail(); } @override Widget build(BuildContext context, RegisterViewModel viewModel) => - _buildBodyChildren(viewModel); + _buildScaffoldWrapper(viewModel); - Widget _buildBodyChildren(RegisterViewModel viewModel) => - SingleChildScrollView( - child: _buildBodyColumn(viewModel), + Widget _buildScaffoldWrapper(RegisterViewModel viewModel) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(viewModel), ); - Widget _buildBodyColumn(RegisterViewModel viewModel) => Column( - mainAxisSize: MainAxisSize.min, + Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack( + children: [ + _buildScaffold(viewModel), + _buildRegistrationState(viewModel), + ], + ); + + Widget _buildScaffold(RegisterViewModel viewModel) => Column( crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(viewModel), + ); + + List _buildScaffoldChildren(RegisterViewModel viewModel) => + [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; + + Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody(RegisterViewModel viewModel) => + Expanded(child: _buildColumnScroller(viewModel)); + + Widget _buildColumnScroller(RegisterViewModel viewModel) => + SingleChildScrollView( + child: _buildBodyWrapper(viewModel), + ); + + Widget _buildBodyWrapper(RegisterViewModel viewModel) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(viewModel), + ); + + Widget _buildBody(RegisterViewModel viewModel) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: _buildBodyColumnChildren(viewModel), ); @@ -248,4 +284,9 @@ class CreatePasswordScreen extends ViewModelWidget { ? kcPrimaryColor : kcPrimaryColor.withOpacity(0.1), ); + + Widget _buildRegistrationState(RegisterViewModel viewModel) => + viewModel.busy(StateObjects.registerWithEmail) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/views/register/screens/register_with_email_screen.dart b/lib/ui/views/register/screens/register_with_email_screen.dart index 1b67670..a434168 100644 --- a/lib/ui/views/register/screens/register_with_email_screen.dart +++ b/lib/ui/views/register/screens/register_with_email_screen.dart @@ -6,7 +6,9 @@ import 'package:yimaru_app/ui/widgets/login_account.dart'; import '../../../common/app_colors.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/option_text_divider.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../register_viewmodel.dart'; import '../register_view.form.dart'; @@ -28,23 +30,95 @@ class RegisterWithEmailScreen extends ViewModelWidget { viewModel.goTo(page: 2, type: RegistrationType.email); } + Widget getPadding(context) { + double half = screenHeight(context) / 2; + return SizedBox( + height: half + 155 - half, + ); + } + @override Widget build(BuildContext context, RegisterViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildBody(RegisterViewModel viewModel) => Column( + Widget _buildScaffoldWrapper( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(context: context, viewModel: viewModel), + ); + + Widget _buildScaffoldStack( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Stack( + children: [ + _buildScaffold(context: context, viewModel: viewModel), + _buildRegisterWithGoogleState(viewModel) + ], + ); + + Widget _buildScaffold( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + _buildScaffoldChildren(context: context, viewModel: viewModel), + ); + + List _buildScaffoldChildren( + {required BuildContext context, + required RegisterViewModel viewModel}) => + [ + _buildAppBar(viewModel), + _buildExpandedBody(context: context, viewModel: viewModel) + ]; + + Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Expanded( + child: _buildColumnScroller(context: context, viewModel: viewModel)); + + Widget _buildColumnScroller( + {required BuildContext context, + required RegisterViewModel viewModel}) => + SingleChildScrollView( + child: _buildBodyWrapper(context: context, viewModel: viewModel), + ); + + Widget _buildBodyWrapper( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context, viewModel: viewModel), + ); + + Widget _buildBody( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), + children: _buildBodyChildren(context: context, viewModel: viewModel), ); - List _buildBodyChildren(RegisterViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildLowerColumn(viewModel)]; - - Widget _buildColumnScroller(RegisterViewModel viewModel) => - SingleChildScrollView( - child: _buildUpperColumn(viewModel), - ); + List _buildBodyChildren( + {required BuildContext context, + required RegisterViewModel viewModel}) => + [ + _buildUpperColumn(viewModel), + getPadding(context), + _buildLowerColumn(viewModel) + ]; Widget _buildUpperColumn(RegisterViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, @@ -108,6 +182,7 @@ class RegisterWithEmailScreen extends ViewModelWidget { List _buildLowerColumnChildren(RegisterViewModel viewModel) => [ _buildContinueButton(viewModel), + _buildRegisterWithGoogleButton(viewModel), _buildOptionTextDivider(), _buildRegisterWithEmailButton(viewModel), verticalSpaceMedium @@ -116,6 +191,7 @@ class RegisterWithEmailScreen extends ViewModelWidget { Widget _buildContinueButton(RegisterViewModel viewModel) => CustomElevatedButton( height: 55, + safe: false, text: 'Continue', borderRadius: 12, foregroundColor: kcWhite, @@ -129,6 +205,18 @@ class RegisterWithEmailScreen extends ViewModelWidget { : kcPrimaryColor.withOpacity(0.1), ); + Widget _buildRegisterWithGoogleButton(RegisterViewModel viewModel) => + CustomElevatedButton( + height: 55, + borderRadius: 12, + backgroundColor: kcWhite, + text: 'Login with Google', + borderColor: kcPrimaryColor, + foregroundColor: kcPrimaryColor, + leadingImage: 'assets/icons/google.png', + onTap: () async => await viewModel.registerWithGoogle(), + ); + Widget _buildOptionTextDivider() => const OptionTextDivider(); Widget _buildRegisterWithEmailButton(RegisterViewModel viewModel) => @@ -142,4 +230,11 @@ class RegisterWithEmailScreen extends ViewModelWidget { text: 'Register with Phone Number', onTap: () => viewModel.goTo(page: 1), ); + + + + Widget _buildRegisterWithGoogleState(RegisterViewModel viewModel) => + viewModel.busy(StateObjects.registerWithEmail) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/views/register/screens/register_with_phone_number_screen.dart b/lib/ui/views/register/screens/register_with_phone_number_screen.dart index 8ab26a5..1d5074e 100644 --- a/lib/ui/views/register/screens/register_with_phone_number_screen.dart +++ b/lib/ui/views/register/screens/register_with_phone_number_screen.dart @@ -8,6 +8,7 @@ import '../../../common/app_colors.dart'; import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; import '../../../widgets/phone_number_prefix.dart'; import '../register_viewmodel.dart'; import '../register_view.form.dart'; @@ -17,23 +18,83 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget { const RegisterWithPhoneNumberScreen( {super.key, required this.phoneNumberController}); + Widget getPadding(context) { + double half = screenHeight(context) / 2; + return SizedBox(height: half + 170 - half); + } + @override Widget build(BuildContext context, RegisterViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildBody(RegisterViewModel viewModel) => Column( + Widget _buildScaffoldWrapper( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(context: context, viewModel: viewModel), + ); + + Widget _buildScaffold( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: + _buildScaffoldChildren(context: context, viewModel: viewModel), + ); + + List _buildScaffoldChildren( + {required BuildContext context, + required RegisterViewModel viewModel}) => + [ + _buildAppBar(viewModel), + _buildExpandedBody(context: context, viewModel: viewModel) + ]; + + Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Expanded( + child: _buildColumnScroller(context: context, viewModel: viewModel)); + + Widget _buildColumnScroller( + {required BuildContext context, + required RegisterViewModel viewModel}) => + SingleChildScrollView( + child: _buildBodyWrapper(context: context, viewModel: viewModel), + ); + + Widget _buildBodyWrapper( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context, viewModel: viewModel), + ); + + Widget _buildBody( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), + children: _buildBodyChildren(context: context, viewModel: viewModel), ); - List _buildBodyChildren(RegisterViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildLowerColumn(viewModel)]; - - Widget _buildColumnScroller(RegisterViewModel viewModel) => - SingleChildScrollView( - child: _buildUpperColumn(viewModel), - ); + List _buildBodyChildren( + {required BuildContext context, + required RegisterViewModel viewModel}) => + [ + _buildUpperColumn(viewModel), + getPadding(context), + _buildLowerColumn(viewModel) + ]; Widget _buildUpperColumn(RegisterViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/ui/views/register/screens/registration_otp_screen.dart b/lib/ui/views/register/screens/registration_otp_screen.dart index 7a8f547..d156c30 100644 --- a/lib/ui/views/register/screens/registration_otp_screen.dart +++ b/lib/ui/views/register/screens/registration_otp_screen.dart @@ -7,9 +7,12 @@ import 'package:yimaru_app/ui/views/register/register_viewmodel.dart'; import 'package:yimaru_app/ui/widgets/custom_cursor.dart'; import '../../../common/app_colors.dart'; +import '../../../common/enmus.dart'; import '../../../common/ui_helpers.dart'; import '../../../widgets/custom_elevated_button.dart'; +import '../../../widgets/large_app_bar.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../register_view.form.dart'; class RegistrationOtpScreen extends ViewModelWidget { @@ -23,6 +26,12 @@ class RegistrationOtpScreen extends ViewModelWidget { required this.emailController, required this.phoneNumberController}); + + Widget getPadding(context){ + double half = screenHeight(context)/2; + return SizedBox(height: half + 325 - half,); + } + Future _verifyOtp(RegisterViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); @@ -36,24 +45,71 @@ class RegistrationOtpScreen extends ViewModelWidget { await viewModel.verifyOtp(); } + @override Widget build(BuildContext context, RegisterViewModel viewModel) => - _buildBody(viewModel); + _buildScaffoldWrapper(context: context, viewModel: viewModel); - Widget _buildBody(RegisterViewModel viewModel) => Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildBodyChildren(viewModel), + Widget _buildScaffoldWrapper( {required BuildContext context, + required RegisterViewModel viewModel}) => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffoldStack(context: context, viewModel: viewModel), + ); + + Widget _buildScaffoldStack( + {required BuildContext context, + required RegisterViewModel viewModel}) => + Stack( + children: [ + _buildScaffold(context: context, viewModel: viewModel), + _buildVerifyOtpState(viewModel) + ], ); - List _buildBodyChildren(RegisterViewModel viewModel) => - [_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)]; + Widget _buildScaffold( {required BuildContext context, + required RegisterViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildScaffoldChildren(context: context, viewModel: viewModel), + ); - Widget _buildColumnScroller(RegisterViewModel viewModel) => + List _buildScaffoldChildren( {required BuildContext context, + required RegisterViewModel viewModel}) => + [_buildAppBar(viewModel), _buildExpandedBody(context: context, viewModel: viewModel)]; + + Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar( + showBackButton: false, + showLanguageSelection: true, + ); + + Widget _buildExpandedBody( {required BuildContext context, + required RegisterViewModel viewModel}) => + Expanded(child: _buildColumnScroller(context: context, viewModel: viewModel)); + + Widget _buildColumnScroller( {required BuildContext context, + required RegisterViewModel viewModel}) => SingleChildScrollView( - child: _buildUpperColumn(viewModel), + child: _buildBodyWrapper(context: context, viewModel: viewModel), ); + Widget _buildBodyWrapper( {required BuildContext context, + required RegisterViewModel viewModel}) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildBody(context: context, viewModel: viewModel), + ); + + Widget _buildBody( {required BuildContext context, + required RegisterViewModel viewModel}) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: _buildBodyChildren(context: context, viewModel: viewModel), + ); + + List _buildBodyChildren( {required BuildContext context, + required RegisterViewModel viewModel}) => + [_buildUpperColumn(viewModel),getPadding(context), _buildContinueButtonWrapper(viewModel)]; + + + Widget _buildUpperColumn(RegisterViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -174,4 +230,9 @@ class RegistrationOtpScreen extends ViewModelWidget { ? () async => await _verifyOtp(viewModel) : null, ); + + Widget _buildVerifyOtpState(RegisterViewModel viewModel) => + viewModel.busy(StateObjects.verifyOtp) + ? const PageLoadingIndicator() + : Container(); } diff --git a/lib/ui/widgets/cancel_learn_practice_sheet.dart b/lib/ui/widgets/cancel_learn_practice_sheet.dart new file mode 100644 index 0000000..632559c --- /dev/null +++ b/lib/ui/widgets/cancel_learn_practice_sheet.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart'; + +import '../common/app_colors.dart'; +import '../common/ui_helpers.dart'; +import 'custom_bottom_sheet.dart'; +import 'custom_elevated_button.dart'; + +class CancelLearnPracticeSheet extends StatelessWidget { + final GestureTapCallback? onTap; + + const CancelLearnPracticeSheet({super.key, this.onTap}); + + @override + Widget build(BuildContext context) => _buildSheetWrapper(); + + Widget _buildSheetWrapper() => CustomBottomSheet( + height: 500, onTap: onTap, child: _buildColumnWrapper()); + + Widget _buildColumnWrapper() => Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: _buildSheetChildren(), + ); + + List _buildSheetChildren() => [ + verticalSpaceLarge, + _buildImage(), + verticalSpaceMedium, + _buildMessage(), + _buildSubtitle(), + verticalSpaceLarge, + _buildContinueButton(), + _buildEndButton(), + + ]; + + Widget _buildImage() => const SpeakingPartnerImage( + radius: 45, + ); + + Widget _buildMessage() => Text.rich( + TextSpan(text: 'You’re almost there,', style: style18DG600, children: [ + TextSpan( + text: ' Johnny!', + style: style18P600, + ) + ]), + ); + + Widget _buildSubtitle() => Text( + 'Finish this session to see your progress.', + style: style14DG400, + textAlign: TextAlign.center, + ); + + Widget _buildContinueButton() => CustomElevatedButton( + height: 55, + onTap: onTap, + borderRadius: 12, + foregroundColor: kcWhite, + text: 'Continue Practice', + backgroundColor: kcPrimaryColor, + ); + + Widget _buildEndButton() => CustomElevatedButton( + height: 55, + onTap: onTap, + borderRadius: 12, + text: 'End Session', + backgroundColor: kcWhite, + borderColor: kcPrimaryColor, + foregroundColor: kcPrimaryColor, + ); +} diff --git a/lib/ui/widgets/custom_bottom_sheet.dart b/lib/ui/widgets/custom_bottom_sheet.dart index 619bfcb..5deb5f0 100644 --- a/lib/ui/widgets/custom_bottom_sheet.dart +++ b/lib/ui/widgets/custom_bottom_sheet.dart @@ -3,15 +3,17 @@ import 'package:yimaru_app/ui/common/app_colors.dart'; class CustomBottomSheet extends StatelessWidget { final Widget child; + final double height; final GestureTapCallback? onTap; - const CustomBottomSheet({super.key, this.onTap, required this.child}); + const CustomBottomSheet( + {super.key, this.onTap, required this.child, required this.height}); @override Widget build(BuildContext context) => _buildStackWrapper(); Widget _buildStackWrapper() => Container( - height: 400, + height: height, color: kcTransparent, width: double.maxFinite, child: _buildStack(), @@ -49,8 +51,8 @@ class CustomBottomSheet extends StatelessWidget { ); Widget _buildSheetWrapper() => Container( - height: double.maxFinite, width: double.maxFinite, + height: double.maxFinite, decoration: const BoxDecoration( color: kcBackgroundColor, borderRadius: BorderRadius.only( diff --git a/lib/ui/widgets/custom_column_button.dart b/lib/ui/widgets/custom_column_button.dart new file mode 100644 index 0000000..b1bf3d2 --- /dev/null +++ b/lib/ui/widgets/custom_column_button.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; + +class CustomColumnButton extends StatelessWidget { + final Color color; + final String label; + final IconData icon; + final GestureTapCallback? onTap; + + const CustomColumnButton( + {super.key, + this.onTap, + required this.icon, + required this.label, + required this.color}); + + @override + Widget build(BuildContext context) => _buildColumnWrapper(); + + Widget _buildColumnWrapper() => GestureDetector( + onTap: onTap, + child: _buildColumn(), + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: _buildColumnChildren(), + ); + + List _buildColumnChildren() => [ + _buildIconWrapper(), + _buildLabel() + ]; + + Widget _buildIconWrapper() => Container( + padding:const EdgeInsets.all(5), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color.withOpacity(0.1), + border: Border.all(color: color.withOpacity(0.75)) + ), + child: _buildIcon(), + ); + + Widget _buildLabel()=> Text(label,style: style14LG400.copyWith(color: color),); + + Widget _buildIcon()=> Icon(icon,size: 14,color: color,); +} diff --git a/lib/ui/widgets/empty_video_player.dart b/lib/ui/widgets/empty_video_player.dart new file mode 100644 index 0000000..de49bf4 --- /dev/null +++ b/lib/ui/widgets/empty_video_player.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +import '../common/app_colors.dart'; +import 'custom_circular_progress_indicator.dart'; + +class EmptyVideoPlayer extends StatelessWidget { + const EmptyVideoPlayer({super.key}); + + @override + Widget build(BuildContext context) => _buildContainer(); + + Widget _buildContainer() => Container( + decoration: const BoxDecoration(color: kcLightGrey), + child: _buildStack(), + ); + + Widget _buildStack() => Stack( + children: [_buildProgressIndicatorWrapper()], + ); + + Widget _buildProgressIndicatorWrapper() => Align( + alignment: Alignment.center, + child: _buildProgressIndicator(), + ); + + Widget _buildProgressIndicator() => + const CustomCircularProgressIndicator(color: kcPrimaryColor); +} diff --git a/lib/ui/widgets/finish_practice_sheet.dart b/lib/ui/widgets/finish_practice_sheet.dart index faa065d..27c0e21 100644 --- a/lib/ui/widgets/finish_practice_sheet.dart +++ b/lib/ui/widgets/finish_practice_sheet.dart @@ -13,8 +13,8 @@ class FinishPracticeSheet extends StatelessWidget { @override Widget build(BuildContext context) => _buildSheetWrapper(); - Widget _buildSheetWrapper() => - CustomBottomSheet(onTap: onTap, child: _buildColumnWrapper()); + Widget _buildSheetWrapper() => CustomBottomSheet( + height: 400, onTap: onTap, child: _buildColumnWrapper()); Widget _buildColumnWrapper() => Padding( padding: const EdgeInsets.symmetric(horizontal: 15), diff --git a/lib/ui/widgets/learn_lesson_tile.dart b/lib/ui/widgets/learn_lesson_tile.dart index 63fb6be..ef60c78 100644 --- a/lib/ui/widgets/learn_lesson_tile.dart +++ b/lib/ui/widgets/learn_lesson_tile.dart @@ -12,9 +12,11 @@ class LearnLessonTile extends StatelessWidget { final String title; final String thumbnail; final ProgressStatuses status; + final GestureTapCallback? onLessonTap; const LearnLessonTile({ super.key, + this.onLessonTap, required this.title, required this.status, required this.thumbnail, @@ -46,7 +48,6 @@ class LearnLessonTile extends StatelessWidget { collapsedIconColor: kcDarkGrey, collapsedTextColor: kcDarkGrey, leading: _buildLeadingWrapper(), - shape: Border.all(color: kcTransparent), expandedAlignment: Alignment.centerLeft, enabled: status != ProgressStatuses.pending, @@ -180,7 +181,7 @@ class LearnLessonTile extends StatelessWidget { _buildLessonButton(), ]; - Widget _buildLessonButton() => const CustomElevatedButton( + Widget _buildPracticeButton() => const CustomElevatedButton( height: 15, width: 135, text: 'Practice', @@ -191,10 +192,11 @@ class LearnLessonTile extends StatelessWidget { foregroundColor: kcPrimaryColor, ); - Widget _buildPracticeButton() => CustomElevatedButton( + Widget _buildLessonButton() => CustomElevatedButton( height: 15, width: 135, borderRadius: 12, + onTap: onLessonTap, foregroundColor: kcWhite, trailingIcon: Icons.play_arrow, backgroundColor: kcPrimaryColor, diff --git a/lib/ui/widgets/profile_image.dart b/lib/ui/widgets/profile_image.dart index 604fa07..043ba49 100644 --- a/lib/ui/widgets/profile_image.dart +++ b/lib/ui/widgets/profile_image.dart @@ -34,7 +34,7 @@ class ProfileImage extends StatelessWidget { Widget _buildProfileImage() => CircleAvatar( radius: 50, - backgroundColor: kcPrimaryColor, + backgroundColor: kcViolet, backgroundImage: loading ? null : profileImage != null || (profileImage?.contains('.') ?? false) diff --git a/lib/ui/widgets/speaking_partner_image.dart b/lib/ui/widgets/speaking_partner_image.dart new file mode 100644 index 0000000..0d8c4ad --- /dev/null +++ b/lib/ui/widgets/speaking_partner_image.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; + +import '../common/app_colors.dart'; + +class SpeakingPartnerImage extends StatelessWidget { + final double radius; + const SpeakingPartnerImage({super.key,required this.radius}); + + @override + Widget build(BuildContext context) => _buildProfileImage(); + + + Widget _buildProfileImage() => CircleAvatar( + radius: radius, + backgroundColor: kcViolet, + backgroundImage: _buildImageBuilder(), + ); + + AssetImage? _buildImageBuilder() => const AssetImage('assets/images/profile.png'); +} diff --git a/lib/ui/widgets/suggestion_card.dart b/lib/ui/widgets/suggestion_card.dart index 88fd382..c4d53d7 100644 --- a/lib/ui/widgets/suggestion_card.dart +++ b/lib/ui/widgets/suggestion_card.dart @@ -20,7 +20,7 @@ class SuggestionCard extends StatelessWidget { gradient: const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, - colors: [kcPrimaryAccent, kcPrimaryColor]), + colors: [kcIndigo, kcPrimaryColor]), ), child: _buildRow(), ); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cb143e7..117f4a9 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,7 +11,10 @@ import file_selector_macos import firebase_core import flutter_secure_storage_darwin import google_sign_in_ios +import package_info_plus import sqflite_darwin +import video_player_avfoundation +import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) @@ -20,5 +23,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) 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 f155710..e845c98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + chewie: + dependency: "direct main" + description: + name: chewie + sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca" + url: "https://pub.dev" + source: hosted + version: "1.13.0" clock: dependency: transitive description: @@ -257,6 +265,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" dart_style: dependency: transitive description: @@ -494,6 +510,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f" + url: "https://pub.dev" + source: hosted + version: "5.2.2" flutter_svg: dependency: "direct main" description: @@ -952,6 +976,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d + url: "https://pub.dev" + source: hosted + version: "9.0.0" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: "direct main" description: @@ -1413,6 +1453,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: ee4fd520b0cafa02e4a867a0f882092e727cdaa1a2d24762171e787f8a502b0a + url: "https://pub.dev" + source: hosted + version: "2.9.1" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: f46e9e20f1fe429760cf4dc118761336320d1bec0f50d255930c2355f2defb5b + url: "https://pub.dev" + source: hosted + version: "2.9.1" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" + url: "https://pub.dev" + source: hosted + version: "6.6.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" + url: "https://pub.dev" + source: hosted + version: "2.4.0" vm_service: dependency: transitive description: @@ -1421,6 +1501,22 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" + url: "https://pub.dev" + source: hosted + version: "1.4.0" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + url: "https://pub.dev" + source: hosted + version: "1.3.0" watcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index eb15793..e80786b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: pinput: ^6.0.1 stacked: ^3.4.0 iconsax: ^0.0.8 + chewie: ^1.13.0 flutter_svg: ^2.2.3 stacked_shared: any image_picker: ^1.2.1 @@ -22,6 +23,7 @@ dependencies: storage_info: ^1.0.0 flutter_html: ^3.0.0 email_validator: any + video_player: ^2.10.1 firebase_core: ^4.4.0 in_app_update: ^4.2.5 path_provider: ^2.1.5 @@ -29,6 +31,7 @@ dependencies: toastification: ^3.0.3 dropdown_search: ^6.0.2 json_annotation: ^4.9.0 + flutter_spinkit: ^5.2.2 stacked_services: ^1.1.0 omni_datetime_picker: any json_serializable: ^6.8.0 diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index 3cfd350..5762ae1 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -952,7 +952,7 @@ class MockAuthenticationService extends _i1.Mock /// See the documentation for Mockito's code generation for more information. class MockApiService extends _i1.Mock implements _i12.ApiService { @override - _i8.Future> register(Map? data) => + _i8.Future> registerWithEmail(Map? data) => (super.noSuchMethod( Invocation.method( #register, @@ -978,7 +978,7 @@ class MockApiService extends _i1.Mock implements _i12.ApiService { ) as _i8.Future>); @override - _i8.Future> googleLogin(Map? data) => + _i8.Future> googleAuth(Map? data) => (super.noSuchMethod( Invocation.method( #googleLogin, @@ -1384,7 +1384,7 @@ class MockGoogleAuthService extends _i1.Mock implements _i19.GoogleAuthService { ) as _i4.GoogleSignIn); @override - _i8.Future<_i4.GoogleSignInAccount?> googleSignIn() => (super.noSuchMethod( + _i8.Future<_i4.GoogleSignInAccount?> googleAuth() => (super.noSuchMethod( Invocation.method( #googleSignIn, [], diff --git a/test/viewmodels/learn_lesson_detail_viewmodel_test.dart b/test/viewmodels/learn_lesson_detail_viewmodel_test.dart new file mode 100644 index 0000000..aef7398 --- /dev/null +++ b/test/viewmodels/learn_lesson_detail_viewmodel_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('LearnLessonDetailViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/viewmodels/learn_practice_viewmodel_test.dart b/test/viewmodels/learn_practice_viewmodel_test.dart new file mode 100644 index 0000000..3336880 --- /dev/null +++ b/test/viewmodels/learn_practice_viewmodel_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('LearnPracticeViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}