diff --git a/lib/app/app.dart b/lib/app/app.dart index cd406ac..938bae2 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -30,6 +30,8 @@ import 'package:yimaru_app/services/status_checker_service.dart'; import 'package:yimaru_app/ui/views/welcome/welcome_view.dart'; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart'; import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'; +import 'package:yimaru_app/ui/views/failure/failure_view.dart'; +import 'package:yimaru_app/services/image_picker_service.dart'; // @stacked-import @StackedApp( @@ -57,6 +59,7 @@ import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'; MaterialRoute(page: WelcomeView), MaterialRoute(page: AssessmentView), MaterialRoute(page: LearnLessonView), + MaterialRoute(page: FailureView), // @stacked-route ], dependencies: [ @@ -68,6 +71,7 @@ import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart'; LazySingleton(classType: SecureStorageService), LazySingleton(classType: DioService), LazySingleton(classType: StatusCheckerService), + LazySingleton(classType: ImagePickerService), // @stacked-service ], bottomsheets: [ diff --git a/lib/app/app.locator.dart b/lib/app/app.locator.dart index 4319ba3..c289fa1 100644 --- a/lib/app/app.locator.dart +++ b/lib/app/app.locator.dart @@ -14,6 +14,7 @@ import 'package:stacked_shared/stacked_shared.dart'; import '../services/api_service.dart'; import '../services/authentication_service.dart'; import '../services/dio_service.dart'; +import '../services/image_picker_service.dart'; import '../services/secure_storage_service.dart'; import '../services/status_checker_service.dart'; @@ -36,4 +37,5 @@ Future setupLocator({ locator.registerLazySingleton(() => SecureStorageService()); locator.registerLazySingleton(() => DioService()); locator.registerLazySingleton(() => StatusCheckerService()); + locator.registerLazySingleton(() => ImagePickerService()); } diff --git a/lib/app/app.router.dart b/lib/app/app.router.dart index 871f60e..53e8dc1 100644 --- a/lib/app/app.router.dart +++ b/lib/app/app.router.dart @@ -5,16 +5,17 @@ // ************************************************************************** // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:flutter/material.dart' as _i25; +import 'package:flutter/material.dart' as _i26; import 'package:flutter/material.dart'; import 'package:stacked/stacked.dart' as _i1; -import 'package:stacked_services/stacked_services.dart' as _i26; +import 'package:stacked_services/stacked_services.dart' as _i27; 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; import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' as _i13; import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; +import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i25; import 'package:yimaru_app/ui/views/home/home_view.dart' as _i2; import 'package:yimaru_app/ui/views/language/language_view.dart' as _i14; import 'package:yimaru_app/ui/views/learn/learn_view.dart' as _i19; @@ -89,6 +90,8 @@ class Routes { static const learnLessonView = '/learn-lesson-view'; + static const failureView = '/failure-view'; + static const all = { homeView, onboardingView, @@ -113,6 +116,7 @@ class Routes { welcomeView, assessmentView, learnLessonView, + failureView, }; } @@ -210,17 +214,21 @@ class StackedRouter extends _i1.RouterBase { Routes.learnLessonView, page: _i24.LearnLessonView, ), + _i1.RouteDef( + Routes.failureView, + page: _i25.FailureView, + ), ]; final _pagesMap = { _i2.HomeView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i2.HomeView(), settings: data, ); }, _i3.OnboardingView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i3.OnboardingView(), settings: data, ); @@ -229,133 +237,141 @@ class StackedRouter extends _i1.RouterBase { final args = data.getArgs( orElse: () => const StartupViewArguments(), ); - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => _i4.StartupView(key: args.key, label: args.label), settings: data, ); }, _i5.ProfileView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i5.ProfileView(), settings: data, ); }, _i6.ProfileDetailView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i6.ProfileDetailView(), settings: data, ); }, _i7.DownloadsView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i7.DownloadsView(), settings: data, ); }, _i8.ProgressView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i8.ProgressView(), settings: data, ); }, _i9.OngoingProgressView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i9.OngoingProgressView(), settings: data, ); }, _i10.AccountPrivacyView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i10.AccountPrivacyView(), settings: data, ); }, _i11.SupportView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i11.SupportView(), settings: data, ); }, _i12.TelegramSupportView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i12.TelegramSupportView(), settings: data, ); }, _i13.CallSupportView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i13.CallSupportView(), settings: data, ); }, _i14.LanguageView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i14.LanguageView(), settings: data, ); }, _i15.PrivacyPolicyView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i15.PrivacyPolicyView(), settings: data, ); }, _i16.TermsAndConditionsView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i16.TermsAndConditionsView(), settings: data, ); }, _i17.RegisterView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i17.RegisterView(), settings: data, ); }, _i18.LoginView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i18.LoginView(), settings: data, ); }, _i19.LearnView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i19.LearnView(), settings: data, ); }, _i20.LearnLevelView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i20.LearnLevelView(), settings: data, ); }, _i21.LearnModuleView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i21.LearnModuleView(), settings: data, ); }, _i22.WelcomeView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i22.WelcomeView(), settings: data, ); }, _i23.AssessmentView: (data) { final args = data.getArgs(nullOk: false); - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => _i23.AssessmentView(key: args.key, data: args.data), settings: data, ); }, _i24.LearnLessonView: (data) { - return _i25.MaterialPageRoute( + return _i26.MaterialPageRoute( builder: (context) => const _i24.LearnLessonView(), settings: data, ); }, + _i25.FailureView: (data) { + final args = data.getArgs(nullOk: false); + return _i26.MaterialPageRoute( + builder: (context) => + _i25.FailureView(key: args.key, label: args.label), + settings: data, + ); + }, }; @override @@ -371,7 +387,7 @@ class StartupViewArguments { this.label = 'Loading', }); - final _i25.Key? key; + final _i26.Key? key; final String label; @@ -398,7 +414,7 @@ class AssessmentViewArguments { required this.data, }); - final _i25.Key? key; + final _i26.Key? key; final Map data; @@ -419,7 +435,34 @@ class AssessmentViewArguments { } } -extension NavigatorStateExtension on _i26.NavigationService { +class FailureViewArguments { + const FailureViewArguments({ + this.key, + required this.label, + }); + + final _i26.Key? key; + + final String label; + + @override + String toString() { + return '{"key": "$key", "label": "$label"}'; + } + + @override + bool operator ==(covariant FailureViewArguments other) { + if (identical(this, other)) return true; + return other.key == key && other.label == label; + } + + @override + int get hashCode { + return key.hashCode ^ label.hashCode; + } +} + +extension NavigatorStateExtension on _i27.NavigationService { Future navigateToHomeView([ int? routerId, bool preventDuplicates = true, @@ -449,7 +492,7 @@ extension NavigatorStateExtension on _i26.NavigationService { } Future navigateToStartupView({ - _i25.Key? key, + _i26.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -718,7 +761,7 @@ extension NavigatorStateExtension on _i26.NavigationService { } Future navigateToAssessmentView({ - _i25.Key? key, + _i26.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -748,6 +791,23 @@ extension NavigatorStateExtension on _i26.NavigationService { transition: transition); } + Future navigateToFailureView({ + _i26.Key? key, + required String label, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return navigateTo(Routes.failureView, + arguments: FailureViewArguments(key: key, label: label), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } + Future replaceWithHomeView([ int? routerId, bool preventDuplicates = true, @@ -777,7 +837,7 @@ extension NavigatorStateExtension on _i26.NavigationService { } Future replaceWithStartupView({ - _i25.Key? key, + _i26.Key? key, String label = 'Loading', int? routerId, bool preventDuplicates = true, @@ -1046,7 +1106,7 @@ extension NavigatorStateExtension on _i26.NavigationService { } Future replaceWithAssessmentView({ - _i25.Key? key, + _i26.Key? key, required Map data, int? routerId, bool preventDuplicates = true, @@ -1075,4 +1135,21 @@ extension NavigatorStateExtension on _i26.NavigationService { parameters: parameters, transition: transition); } + + Future replaceWithFailureView({ + _i26.Key? key, + required String label, + int? routerId, + bool preventDuplicates = true, + Map? parameters, + Widget Function(BuildContext, Animation, Animation, Widget)? + transition, + }) async { + return replaceWith(Routes.failureView, + arguments: FailureViewArguments(key: key, label: label), + id: routerId, + preventDuplicates: preventDuplicates, + parameters: parameters, + transition: transition); + } } diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart index a890509..de05dc5 100644 --- a/lib/models/user_model.dart +++ b/lib/models/user_model.dart @@ -4,9 +4,13 @@ part 'user_model.g.dart'; @JsonSerializable() class UserModel { + final String? firstName; + @JsonKey(name: 'user_id') final int? userId; + final String? profileImage; + final bool? profileCompleted; @JsonKey(name: 'access_token') @@ -15,11 +19,14 @@ class UserModel { @JsonKey(name: 'refresh_token') final String? refreshToken; - const UserModel( - {this.userId, - this.accessToken, - this.profileCompleted, - this.refreshToken}); + const UserModel({ + this.userId, + this.firstName, + this.accessToken, + this.profileImage, + this.refreshToken, + this.profileCompleted, + }); factory UserModel.fromJson(Map json) => _$UserModelFromJson(json); diff --git a/lib/models/user_model.g.dart b/lib/models/user_model.g.dart index ac50491..625b7b6 100644 --- a/lib/models/user_model.g.dart +++ b/lib/models/user_model.g.dart @@ -8,13 +8,17 @@ part of 'user_model.dart'; UserModel _$UserModelFromJson(Map json) => UserModel( userId: (json['user_id'] as num?)?.toInt(), + firstName: json['firstName'] as String?, accessToken: json['access_token'] as String?, - profileCompleted: json['profileCompleted'] as bool?, + profileImage: json['profileImage'] as String?, refreshToken: json['refresh_token'] as String?, + profileCompleted: json['profileCompleted'] as bool?, ); Map _$UserModelToJson(UserModel instance) => { + 'firstName': instance.firstName, 'user_id': instance.userId, + 'profileImage': instance.profileImage, 'profileCompleted': instance.profileCompleted, 'access_token': instance.accessToken, 'refresh_token': instance.refreshToken, diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart index fccb70e..80fd409 100644 --- a/lib/services/api_service.dart +++ b/lib/services/api_service.dart @@ -14,7 +14,7 @@ class ApiService { Future> register(Map data) async { try { Response response = await _service.dio.post( - '$baseUrl/$userUrl/$kRegisterUrl', + '$baseUrl/$kUserUrl/$kRegisterUrl', data: data, ); @@ -69,7 +69,7 @@ class ApiService { Future> verifyOtp(Map data) async { try { Response response = await _service.dio.post( - '$baseUrl/$userUrl/$kVerifyOtpUrl', + '$baseUrl/$kUserUrl/$kVerifyOtpUrl', data: data, ); if (response.statusCode == 200) { @@ -96,7 +96,7 @@ class ApiService { Future> resendOtp(Map data) async { try { Response response = await _service.dio.post( - '$baseUrl/$userUrl/$kResendOtpUrl', + '$baseUrl/$kUserUrl/$kResendOtpUrl', data: data, ); @@ -123,7 +123,7 @@ class ApiService { Future> getProfileStatus(UserModel? user) async { try { Response response = await _service.dio.get( - '$baseUrl/$userUrl/${user?.userId}/$kProfileStatusUrl', + '$baseUrl/$kUserUrl/${user?.userId}/$kProfileStatusUrl', ); if (response.statusCode == 200) { @@ -146,18 +146,42 @@ class ApiService { } } + // Get profile + Future> getProfileData(int? userId) async { + try { + Response response = await _service.dio.get( + '$baseUrl/$kUserUrl/$kGetUserUrl/$userId', + ); + + if (response.statusCode == 200) { + return { + 'data': response.data['data'], + 'status': ResponseStatus.success, + 'message': 'Profile fetched successfully' + }; + } else { + return { + 'status': ResponseStatus.failure, + 'message': 'Unknown Error Occurred' + }; + } + } catch (e) { + return { + 'message': e.toString(), + 'status': ResponseStatus.failure, + }; + } + } + // Update profile Future> updateProfile( {required UserModel? user, required Map data}) async { try { Response response = await _service.dio.put( - '$baseUrl/$userUrl', + '$baseUrl/$kUserUrl', data: data, ); - print(response.statusCode); - print(response.data); - if (response.statusCode == 200) { return { 'status': ResponseStatus.success, @@ -170,7 +194,6 @@ class ApiService { }; } } catch (e) { - print('Exception ${e.toString()}'); return { 'message': e.toString(), 'status': ResponseStatus.failure, diff --git a/lib/services/authentication_service.dart b/lib/services/authentication_service.dart index abd0fa6..369e1cc 100644 --- a/lib/services/authentication_service.dart +++ b/lib/services/authentication_service.dart @@ -5,6 +5,10 @@ import 'package:yimaru_app/services/secure_storage_service.dart'; class AuthenticationService { final _secureService = locator(); + UserModel? _user; + + UserModel? get user => _user; + Future userLoggedIn() async { if (await _secureService.getString('userId') != null) { return true; @@ -28,14 +32,56 @@ class AuthenticationService { await _secureService.setString('refreshToken', refresh); } - Future saveUserData(Map data) async { + Future saveUserName(Map data) async { + await _secureService.setString('firstName', data['firstName']); + _user = UserModel( + firstName: await _secureService.getString('firstName'), + userId: _user?.userId, + accessToken: _user?.accessToken, + refreshToken: _user?.refreshToken, + profileCompleted: _user?.profileCompleted); + } + + Future saveBasicUserData(Map data) async { await _secureService.setInt('userId', data['userId']); await _secureService.setString('accessToken', data['accessToken']); await _secureService.setString('refreshToken', data['refreshToken']); + + _user = UserModel( + firstName: _user?.firstName, + profileImage: _user?.profileImage, + profileCompleted: _user?.profileCompleted, + userId: await _secureService.getInt('userId'), + accessToken: await _secureService.getString('accessToken'), + refreshToken: await _secureService.getString('refreshToken'), + ); } - Future saveProfileCompleted(bool value) async { + Future saveProfileStatus(bool value) async { await _secureService.setBool('profileCompleted', value); + + _user = UserModel( + userId: _user?.userId, + firstName: _user?.firstName, + accessToken: _user?.accessToken, + refreshToken: _user?.refreshToken, + profileImage: _user?.profileImage, + profileCompleted: await _secureService.getBool('profileCompleted')); + } + + Future saveProfileImage() async {} + + Future saveFullName(Map data) async { + await _secureService.setBool('profileCompleted', true); + await _secureService.setString('firstName', data['firstName']); + _user = UserModel( + userId: _user?.userId, + accessToken: _user?.accessToken, + refreshToken: _user?.refreshToken, + profileImage: _user?.profileImage, + firstName: await _secureService.getString('firstName'), + profileCompleted: await _secureService.getBool('profileCompleted'), + ); } Future isFirstTimeInstall() async => @@ -45,14 +91,16 @@ class AuthenticationService { await _secureService.setBool('firstTimeInstall', value); } - Future getUser() async { - UserModel user = UserModel( + Future getUser() async { + _user = UserModel( userId: await _secureService.getInt('userId'), + firstName: await _secureService.getString('firstName'), accessToken: await _secureService.getString('accessToken'), refreshToken: await _secureService.getString('refreshToken'), + profileImage: await _secureService.getString('profileImage'), profileCompleted: await _secureService.getBool('profileCompleted'), ); - return user; + return _user; } Future logOut() async { diff --git a/lib/services/dio_service.dart b/lib/services/dio_service.dart index 94fbe33..3760b28 100644 --- a/lib/services/dio_service.dart +++ b/lib/services/dio_service.dart @@ -125,18 +125,17 @@ class DioService { } Future _refreshToken() async { - final UserModel user = await _authenticationService.getUser(); + final UserModel? user = await _authenticationService.getUser(); - if (user.refreshToken == null) return false; + if (user?.refreshToken == null) return false; try { Map data = { 'role': 'STUDENT', - 'user_id': user.userId, - 'access_token': user.accessToken, - 'refresh_token': user.refreshToken + 'user_id': user?.userId, + 'access_token': user?.accessToken, + 'refresh_token': user?.refreshToken }; - print(data); final response = await _refreshDio.post( '$baseUrl/$kRefreshTokenUrl', data: data, @@ -150,8 +149,6 @@ class DioService { ), ); - print('Refresh response'); - print(response.data); await _authenticationService.saveTokens( access: response.data['access_token'], refresh: response.data['refresh_token'], @@ -159,10 +156,8 @@ class DioService { return true; } catch (e) { - print('Refresh response exception'); - print(e.toString()); - // await _authenticationService.logOut(); - // await _navigationService.replaceWithLoginView(); + await _authenticationService.logOut(); + await _navigationService.replaceWithLoginView(); return false; } } diff --git a/lib/services/image_picker_service.dart b/lib/services/image_picker_service.dart new file mode 100644 index 0000000..058cde1 --- /dev/null +++ b/lib/services/image_picker_service.dart @@ -0,0 +1 @@ +class ImagePickerService {} diff --git a/lib/ui/common/app_constants.dart b/lib/ui/common/app_constants.dart index 1223651..1e5af9a 100644 --- a/lib/ui/common/app_constants.dart +++ b/lib/ui/common/app_constants.dart @@ -1,7 +1,9 @@ String baseUrl = 'http://195.35.29.82:8080'; //String baseUrl = 'https://api.yimaru.yaltopia.com'; -String userUrl = 'api/v1/user'; +String kGetUserUrl = 'single'; + +String kUserUrl = 'api/v1/user'; String kRegisterUrl = 'register'; diff --git a/lib/ui/common/ui_helpers.dart b/lib/ui/common/ui_helpers.dart index f4f72f9..1e6b738 100644 --- a/lib/ui/common/ui_helpers.dart +++ b/lib/ui/common/ui_helpers.dart @@ -171,15 +171,9 @@ PinTheme errorPinTheme = defaultPin.copyBorderWith( border: Border.all(color: Colors.red), ); -TextStyle validationStyle = const TextStyle( - fontSize: 12, - color: Colors.red, - fontWeight: FontWeight.w700, -); - -TextStyle style25DG600 = const TextStyle( - fontSize: 25, - color: kcDarkGrey, +TextStyle style18P600 = const TextStyle( + fontSize: 18, + color: kcPrimaryColor, fontWeight: FontWeight.w600, ); @@ -189,6 +183,21 @@ TextStyle style12R700 = const TextStyle( fontWeight: FontWeight.w700, ); +TextStyle style14P400 = const TextStyle( + color: kcPrimaryColor, +); + +TextStyle style14P600 = const TextStyle( + color: kcPrimaryColor, + fontWeight: FontWeight.w600, +); + +TextStyle style25DG600 = const TextStyle( + fontSize: 25, + color: kcDarkGrey, + fontWeight: FontWeight.w600, +); + TextStyle style16DG600 = const TextStyle( fontSize: 16, color: kcDarkGrey, @@ -210,13 +219,15 @@ TextStyle style14DG400 = const TextStyle( color: kcDarkGrey, ); -TextStyle style14P400 = const TextStyle( - color: kcPrimaryColor, +TextStyle style14DG600 = const TextStyle( + color: kcDarkGrey, + fontWeight: FontWeight.w600, ); -TextStyle style14P600 = const TextStyle( - color: kcPrimaryColor, - fontWeight: FontWeight.w600, +TextStyle validationStyle = const TextStyle( + fontSize: 12, + color: Colors.red, + fontWeight: FontWeight.w700, ); Style htmlDefaultStyle = Style(color: kcDarkGrey, fontSize: FontSize(16)); diff --git a/lib/ui/views/assessment/assessment_viewmodel.dart b/lib/ui/views/assessment/assessment_viewmodel.dart index 4506612..5ca21d9 100644 --- a/lib/ui/views/assessment/assessment_viewmodel.dart +++ b/lib/ui/views/assessment/assessment_viewmodel.dart @@ -5,16 +5,20 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; +import '../../../app/app.dialogs.dart'; import '../../../app/app.locator.dart'; import '../../../app/app.router.dart'; import '../../../models/assessment.dart'; import '../../../models/user_model.dart'; import '../../../services/api_service.dart'; import '../../../services/authentication_service.dart'; +import '../../common/app_colors.dart'; +import '../../common/ui_helpers.dart'; import '../home/home_view.dart'; class AssessmentViewModel extends BaseViewModel { final _apiService = locator(); + final _dialogService = locator(); final _navigationService = locator(); final _authenticationService = locator(); @@ -73,9 +77,6 @@ class AssessmentViewModel extends BaseViewModel { if (_currentQuestion == 5) { // A1 final correctCount = countCorrectAnswersUntil(5); - print('All : $_selectedAnswers'); - print('Question page : $_currentQuestion'); - print('Correct A1: $correctCount'); if (correctCount > 3) { return {'continue': true, 'level': ProficiencyLevels.a1}; @@ -86,9 +87,6 @@ class AssessmentViewModel extends BaseViewModel { // A2 final correctCount = countCorrectAnswersUntil(10); - print('All : $_selectedAnswers'); - print('Question page : $_currentQuestion'); - print('Correct A2: $correctCount'); if (correctCount > 3) { return {'continue': true, 'level': ProficiencyLevels.a2}; @@ -98,9 +96,6 @@ class AssessmentViewModel extends BaseViewModel { } else if (_currentQuestion == 16) { // B1 final correctCount = countCorrectAnswersUntil(16); - print('All : $_selectedAnswers'); - print('Question page : $_currentQuestion'); - print('Correct B1: $correctCount'); if (correctCount > 4) { return {'continue': true, 'level': ProficiencyLevels.b1}; @@ -109,9 +104,6 @@ class AssessmentViewModel extends BaseViewModel { } } else if (_currentQuestion == 22) { final correctCount = countCorrectAnswersUntil(16); - print('All : $_selectedAnswers'); - print('Question page : $_currentQuestion'); - print('Correct B2: $correctCount'); if (correctCount > 4) { return {'continue': true, 'level': ProficiencyLevels.b2}; @@ -178,17 +170,26 @@ class AssessmentViewModel extends BaseViewModel { } // Complete profile - Future completeProfile() async { - Map response = - await runBusyFuture>(_completeProfile()); + + Future saveProfileCompleted() async { + Map data = {'firstName': _userData['firstName']}; + await _authenticationService.saveFullName(data); } + Future completeProfile() async => + await runBusyFuture>(_completeProfile()); + Future> _completeProfile() async { - print(_userData); - UserModel user = await _authenticationService.getUser(); + UserModel? user = await _authenticationService.getUser(); Map response = await _apiService.updateProfile(data: _userData, user: user); - + if (response['status'] == ResponseStatus.success) { + showSuccessToast(response['message']); + await saveProfileCompleted(); + await replaceWithHome(); + } else { + showErrorToast(response['message']); + } return response; } @@ -203,8 +204,7 @@ class AssessmentViewModel extends BaseViewModel { } else { if (response['continue']) { _pageController.jumpToPage(_currentQuestion); - } - { + } else { _proficiencyLevel = response['level']; next(); } @@ -218,8 +218,6 @@ class AssessmentViewModel extends BaseViewModel { _pageController.previousPage( duration: const Duration(microseconds: 100), curve: Curves.linear); rebuildUi(); - } else { - _navigationService.back(); } } @@ -238,12 +236,34 @@ class AssessmentViewModel extends BaseViewModel { } void pop() { - if (_currentPage != 0) { + if (_currentPage == 3 /*7*/) { + _navigationService.back(); + } else if (_currentPage != 0 && _currentPage != 3) { _currentPage--; rebuildUi(); } } + Future showAbortDialog() async { + DialogResponse? response = await _dialogService.showDialog( + cancelTitle: 'No', + buttonTitle: 'Yes', + barrierDismissible: true, + title: 'Abort Assessment', + cancelTitleColor: kcDarkGrey, + buttonTitleColor: kcPrimaryColor, + description: 'Are you sure to abort the assessment ?', + ); + return response?.confirmed; + } + + Future abort() async { + bool? response = await showAbortDialog(); + if (response != null && response) { + next(page: 3); + } + } + Future navigateToLanguage() async => await _navigationService.navigateToLanguageView(); diff --git a/lib/ui/views/assessment/screens/assessment_form_screen.dart b/lib/ui/views/assessment/screens/assessment_form_screen.dart index aa88c8f..6fc8965 100644 --- a/lib/ui/views/assessment/screens/assessment_form_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_form_screen.dart @@ -45,9 +45,10 @@ class AssessmentFormScreen extends ViewModelWidget { [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( - showBackButton: true, + onClose: viewModel.abort, showLanguageSelection: false, onPop: viewModel.previousQuestion, + showBackButton: viewModel.currentQuestion == 0 ? false : true, ); Widget _buildExpandedBody(AssessmentViewModel viewModel) => diff --git a/lib/ui/views/assessment/screens/assessment_intro_screen.dart b/lib/ui/views/assessment/screens/assessment_intro_screen.dart index c93fe2e..6052440 100644 --- a/lib/ui/views/assessment/screens/assessment_intro_screen.dart +++ b/lib/ui/views/assessment/screens/assessment_intro_screen.dart @@ -58,7 +58,8 @@ class AssessmentIntroScreen extends ViewModelWidget { ]; Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( - showBackButton: false, + showBackButton: true, + onPop: viewModel.pop, showLanguageSelection: true, onLanguage: () async => await viewModel.navigateToLanguage(), ); diff --git a/lib/ui/views/assessment/screens/start_lesson_screen.dart b/lib/ui/views/assessment/screens/start_lesson_screen.dart index 719f74d..e8b89ff 100644 --- a/lib/ui/views/assessment/screens/start_lesson_screen.dart +++ b/lib/ui/views/assessment/screens/start_lesson_screen.dart @@ -7,6 +7,7 @@ import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart'; import 'package:yimaru_app/ui/widgets/large_app_bar.dart'; import '../../../common/enmus.dart'; +import '../../../widgets/page_loading_indicator.dart'; import '../assessment_viewmodel.dart'; class StartLessonScreen extends ViewModelWidget { @@ -15,13 +16,11 @@ class StartLessonScreen extends ViewModelWidget { Future _start(AssessmentViewModel viewModel) async { if (viewModel.proficiencyLevel != ProficiencyLevels.none) { Map data = { - 'preferred_language': 'en', 'knowledge_level': viewModel.proficiencyLevel.name.toUpperCase() }; viewModel.addUserData(data); } - await viewModel.completeProfile(); } @@ -31,20 +30,24 @@ class StartLessonScreen extends ViewModelWidget { Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold( backgroundColor: kcBackgroundColor, - body: _buildScaffold(viewModel), + body: _buildScaffoldStack(viewModel), ); + Widget _buildScaffoldStack(AssessmentViewModel viewModel) => + Stack(children: [_buildScaffold(viewModel), _buildState(viewModel)]); + Widget _buildScaffold(AssessmentViewModel viewModel) => Column( crossAxisAlignment: CrossAxisAlignment.start, children: _buildScaffoldChildren(viewModel), ); List _buildScaffoldChildren(AssessmentViewModel viewModel) => - [_buildAppBar(), _buildExpandedBody(viewModel)]; + [_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; - Widget _buildAppBar() => const LargeAppBar( - showBackButton: false, - showLanguageSelection: false, + Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( + showBackButton: true, + onPop: viewModel.pop, + showLanguageSelection: true, ); Widget _buildExpandedBody(AssessmentViewModel viewModel) => @@ -114,10 +117,13 @@ class StartLessonScreen extends ViewModelWidget { Widget _buildContinueButton(AssessmentViewModel viewModel) => CustomElevatedButton( height: 55, + text: 'Finish', borderRadius: 12, - text: 'Go to My Lessons', foregroundColor: kcWhite, backgroundColor: kcPrimaryColor, onTap: () async => await _start(viewModel), ); + + Widget _buildState(AssessmentViewModel viewModel) => + viewModel.isBusy ? const PageLoadingIndicator() : Container(); } diff --git a/lib/ui/views/failure/failure_view.dart b/lib/ui/views/failure/failure_view.dart new file mode 100644 index 0000000..e7c9c7a --- /dev/null +++ b/lib/ui/views/failure/failure_view.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stacked/stacked.dart'; + +import '../../common/app_colors.dart'; +import '../../common/ui_helpers.dart'; +import '../../widgets/custom_circular_progress_indicator.dart'; +import 'failure_viewmodel.dart'; + +class FailureView extends StackedView { + final String label; + const FailureView({Key? key, required this.label}) : super(key: key); + + @override + FailureViewModel viewModelBuilder(BuildContext context) => FailureViewModel(); + + @override + Widget builder( + BuildContext context, + FailureViewModel viewModel, + Widget? child, + ) => + _buildScaffoldWrapper(); + + Widget _buildScaffoldWrapper() => Scaffold( + backgroundColor: kcBackgroundColor, + body: _buildScaffold(), + ); + + Widget _buildScaffold() => Stack( + children: _buildScaffoldChildren(), + ); + + List _buildScaffoldChildren() => [ + _buildBackground(), + _buildColumn(), + ]; + + Widget _buildBackground() => Image.asset( + 'assets/images/onboarding_1.png', + fit: BoxFit.fill, + width: double.maxFinite, + height: double.maxFinite, + ); + + Widget _buildColumn() => Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildUpperColumnChildren(), + ); + + List _buildUpperColumnChildren() => + [_buildIconWrapper(), _buildSafeWrapper()]; + + Widget _buildSafeWrapper() => SafeArea(child: _buildLoadingTextContainer()); + + Widget _buildLoadingTextContainer() => Padding( + padding: const EdgeInsets.only(bottom: 50), + child: _buildLoadingTextWrapper(), + ); + + Widget _buildLoadingTextWrapper() => Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: _buildLoadingTextChildren(), + ); + + List _buildLoadingTextChildren() => [ + _buildLoadingText(), + horizontalSpaceSmall, + _buildIndicatorWrapper(), + ]; + + Widget _buildLoadingText() => + Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16)); + + Widget _buildIndicatorWrapper() => SizedBox( + width: 16, + height: 16, + child: _buildIndicator(), + ); + + Widget _buildIndicator() => + const CustomCircularProgressIndicator(color: kcWhite); + + Widget _buildIconWrapper() => Padding( + padding: const EdgeInsets.only(top: 100), + child: _buildIcon(), + ); + + Widget _buildIcon() => SvgPicture.asset( + 'assets/icons/logo.svg', + height: 50, + ); +} diff --git a/lib/ui/views/failure/failure_viewmodel.dart b/lib/ui/views/failure/failure_viewmodel.dart new file mode 100644 index 0000000..b361d69 --- /dev/null +++ b/lib/ui/views/failure/failure_viewmodel.dart @@ -0,0 +1,3 @@ +import 'package:stacked/stacked.dart'; + +class FailureViewModel extends BaseViewModel {} diff --git a/lib/ui/views/home/home_view.dart b/lib/ui/views/home/home_view.dart index 8ed1ff8..ecf2308 100644 --- a/lib/ui/views/home/home_view.dart +++ b/lib/ui/views/home/home_view.dart @@ -15,8 +15,9 @@ class HomeView extends StackedView { HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); @override - void onViewModelReady(HomeViewModel viewModel) { - viewModel.getProfileStatus(); + void onViewModelReady(HomeViewModel viewModel) async { + await viewModel.getProfileStatus(); + await viewModel.getProfileData(); super.onViewModelReady(viewModel); } diff --git a/lib/ui/views/home/home_viewmodel.dart b/lib/ui/views/home/home_viewmodel.dart index 10002ba..bd43cdb 100644 --- a/lib/ui/views/home/home_viewmodel.dart +++ b/lib/ui/views/home/home_viewmodel.dart @@ -7,6 +7,10 @@ import 'package:yimaru_app/services/status_checker_service.dart'; import 'package:yimaru_app/ui/common/app_strings.dart'; import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; +import 'package:yimaru_app/ui/common/ui_helpers.dart'; +import 'package:yimaru_app/ui/views/failure/failure_view.dart'; +import 'package:yimaru_app/ui/views/login/login_view.dart'; +import 'package:yimaru_app/ui/views/startup/startup_view.dart'; import '../../../services/api_service.dart'; import '../../../services/authentication_service.dart'; @@ -46,28 +50,69 @@ class HomeViewModel extends BaseViewModel { ); } + Future saveFullName(String name) async { + Map data = { + 'firstName': name, + }; + await _authenticationService.saveFullName(data); + } + + Future saveProfileStatus(bool value) async => + await _authenticationService.saveProfileStatus(value); + // Navigation + Future replaceWithFailure() async => + await _navigationService.clearStackAndShowView( + const FailureView(label: 'Check your internet connection to proceed'), + ); + Future replaceWithOnboarding() async => await _navigationService.replaceWithOnboardingView(); // Remote api calls + Future getProfileData() async => + await runBusyFuture>(_getProfileData()); + + Future> _getProfileData() async { + Map response = {}; + + UserModel? user = await _authenticationService.getUser(); + + if (user?.profileCompleted != null) { + if (await _statusChecker.checkConnection()) { + response = await _apiService.getProfileData(user?.userId); + if (response['status'] == ResponseStatus.success) { + Map data = { + 'firstName': response['data']['first_name'] + }; + await _authenticationService.saveFullName(data); + } + } + } + + return response; + } + Future getProfileStatus() async { Map response = await runBusyFuture>(_getProfileStatus()); if (response['status'] == ResponseStatus.success && !response['data']) { await replaceWithOnboarding(); + } else if (response['status'] == ResponseStatus.success && + response['data']) { + await saveProfileStatus(response['data']); } } Future> _getProfileStatus() async { Map response = {}; - UserModel user = await _authenticationService.getUser(); - if (user.profileCompleted == null) { + UserModel? user = await _authenticationService.getUser(); + if (user?.profileCompleted == null) { if (await _statusChecker.checkConnection()) { response = await _apiService.getProfileStatus(user); } else { - response = {'data': false, 'status': ResponseStatus.success}; + await replaceWithFailure(); } } else { response = {'data': true, 'status': ResponseStatus.success}; diff --git a/lib/ui/views/learn/learn_view.dart b/lib/ui/views/learn/learn_view.dart index a98b2a9..4805ca8 100644 --- a/lib/ui/views/learn/learn_view.dart +++ b/lib/ui/views/learn/learn_view.dart @@ -38,12 +38,15 @@ class LearnView extends StackedView { Widget _buildColumn(LearnViewModel viewModel) => Column( children: [ verticalSpaceMedium, - _buildAppBar(), + _buildAppBar(viewModel), _buildLevelsColumnWrapper(viewModel) ], ); - Widget _buildAppBar() => const LearnAppBar(); + Widget _buildAppBar(LearnViewModel viewModel) => LearnAppBar( + name: viewModel.user?.firstName, + profileImage: viewModel.user?.profileImage, + ); Widget _buildLevelsColumnWrapper(LearnViewModel viewModel) => Expanded(child: _buildLevelsColumnScrollView(viewModel)); diff --git a/lib/ui/views/learn/learn_viewmodel.dart b/lib/ui/views/learn/learn_viewmodel.dart index 00b418c..83260d1 100644 --- a/lib/ui/views/learn/learn_viewmodel.dart +++ b/lib/ui/views/learn/learn_viewmodel.dart @@ -1,12 +1,19 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; +import 'package:yimaru_app/models/user_model.dart'; +import 'package:yimaru_app/services/authentication_service.dart'; import 'package:yimaru_app/ui/common/enmus.dart'; import '../../../app/app.locator.dart'; class LearnViewModel extends BaseViewModel { final _navigationService = locator(); + final _authenticationService = locator(); + + late final UserModel? _user = _authenticationService.user; + + UserModel? get user => _user; final List> _learnLevels = [ { diff --git a/lib/ui/views/learn_module/learn_module_viewmodel.dart b/lib/ui/views/learn_module/learn_module_viewmodel.dart index 3a9bcd4..5205cb0 100644 --- a/lib/ui/views/learn_module/learn_module_viewmodel.dart +++ b/lib/ui/views/learn_module/learn_module_viewmodel.dart @@ -37,5 +37,6 @@ class LearnModuleViewModel extends BaseViewModel { void pop() => _navigationService.back(); - Future navigateToLearnLesson() async=> await _navigationService.navigateToLearnLessonView(); + Future navigateToLearnLesson() async => + await _navigationService.navigateToLearnLessonView(); } diff --git a/lib/ui/views/login/login_viewmodel.dart b/lib/ui/views/login/login_viewmodel.dart index a58582d..b41a1ee 100644 --- a/lib/ui/views/login/login_viewmodel.dart +++ b/lib/ui/views/login/login_viewmodel.dart @@ -126,7 +126,7 @@ class LoginViewModel extends FormViewModel { 'refreshToken': user.refreshToken }; - await _authenticationService.saveUserData(data); + await _authenticationService.saveBasicUserData(data); showSuccessToast(response['message']); } else { showErrorToast(response['message']); diff --git a/lib/ui/views/onboarding/onboarding_view.dart b/lib/ui/views/onboarding/onboarding_view.dart index 89a3bff..320a40a 100644 --- a/lib/ui/views/onboarding/onboarding_view.dart +++ b/lib/ui/views/onboarding/onboarding_view.dart @@ -57,7 +57,7 @@ class OnboardingView extends StackedView Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) => PopScope( - canPop: false, + canPop: viewModel.currentPage == 0 ? true : false, onPopInvokedWithResult: (value, data) => viewModel.pop(), child: _buildOnboardingScreens(viewModel)); diff --git a/lib/ui/views/onboarding/onboarding_viewmodel.dart b/lib/ui/views/onboarding/onboarding_viewmodel.dart index 7d144db..069cbb5 100644 --- a/lib/ui/views/onboarding/onboarding_viewmodel.dart +++ b/lib/ui/views/onboarding/onboarding_viewmodel.dart @@ -54,10 +54,13 @@ class OnboardingViewModel extends FormViewModel { // Age group final List _ageGroups = [ - '8-14', - '15-18', - '19-26', - '26+', + 'UNDER_13', + '13_17', + '18_24', + '25_34', + '35_44', + '45_54', + '55_PLUS' ]; List get ageGroups => _ageGroups; @@ -352,7 +355,6 @@ class OnboardingViewModel extends FormViewModel { // Add user data void addUserData(Map data) { _userData.addAll(data); - print('User data : $_userData'); } void clearUserData() { @@ -385,7 +387,7 @@ class OnboardingViewModel extends FormViewModel { } void pop() { - if (_currentPage == 8) { + if (_currentPage == 0) { _navigationService.back(); } else { _currentPage--; diff --git a/lib/ui/views/onboarding/screens/birthday_form_screen.dart b/lib/ui/views/onboarding/screens/birthday_form_screen.dart index d832eb9..270b759 100644 --- a/lib/ui/views/onboarding/screens/birthday_form_screen.dart +++ b/lib/ui/views/onboarding/screens/birthday_form_screen.dart @@ -15,11 +15,7 @@ class BirthdayFormScreen extends ViewModelWidget { Future _next(OnboardingViewModel viewModel) async { FocusManager.instance.primaryFocus?.unfocus(); - Map data = { - 'birth_day': DateFormat('yyyy-MM-dd') - .parseUTC(viewModel.selectedBirthday ?? DateTime.now().toString()) - .toIso8601String() - }; + Map data = {'birth_day': viewModel.selectedBirthday}; viewModel.addUserData(data); viewModel.next(); } diff --git a/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart b/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart index 798eda7..f634218 100644 --- a/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart +++ b/lib/ui/views/onboarding/screens/learning_goal_form_screen.dart @@ -93,13 +93,16 @@ class LearningGoalFormScreen extends ViewModelWidget { onLanguage: () async => await viewModel.navigateToLanguage(), ); - Widget _buildTitle(OnboardingViewModel viewModel) => Text( - 'Hi ${viewModel.userData['first_name']}, Choose your learning goal.', - style: const TextStyle( - fontSize: 25, - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), + Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich( + TextSpan( + text: 'Hi ${viewModel.userData['first_name']},', + style: style18P600.copyWith(fontSize: 22), + children: [ + TextSpan( + text: ' Choose your learning goal.', + style: style16DG600.copyWith(fontSize: 22), + ) + ]), ); Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder( diff --git a/lib/ui/views/onboarding/screens/topic_form_screen.dart b/lib/ui/views/onboarding/screens/topic_form_screen.dart index 3bc0f43..a5390a4 100644 --- a/lib/ui/views/onboarding/screens/topic_form_screen.dart +++ b/lib/ui/views/onboarding/screens/topic_form_screen.dart @@ -17,6 +17,8 @@ class TopicFormScreen extends ViewModelWidget { FocusManager.instance.primaryFocus?.unfocus(); Map data = { + 'profile_completed': true, + 'preferred_language': 'en', 'favoutite_topic': viewModel.selectedTopic ?? topicController.text, }; viewModel.addUserData(data); diff --git a/lib/ui/views/profile/profile_view.dart b/lib/ui/views/profile/profile_view.dart index fc79f97..909bd3c 100644 --- a/lib/ui/views/profile/profile_view.dart +++ b/lib/ui/views/profile/profile_view.dart @@ -47,7 +47,7 @@ class ProfileView extends StackedView { children: [ verticalSpaceMedium, _buildNotificationIconWrapper(), - _buildProfileSection(), + _buildProfileSection(viewModel), verticalSpaceSmall, _buildViewProfileButton(viewModel), verticalSpaceLarge, @@ -66,27 +66,25 @@ class ProfileView extends StackedView { color: kcDarkGrey, ); - Widget _buildProfileSection() => Column( + Widget _buildProfileSection(ProfileViewModel viewModel) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, - children: _buildProfileSectionChildren(), + children: _buildProfileSectionChildren(viewModel), ); - List _buildProfileSectionChildren() => [ - _buildProfileImage(), + List _buildProfileSectionChildren(ProfileViewModel viewModel) => [ + _buildProfileImage(viewModel), verticalSpaceSmall, - _buildProfileName(), + _buildProfileName(viewModel), ]; - Widget _buildProfileImage() => const ProfileImage(); + Widget _buildProfileImage(ProfileViewModel viewModel) => ProfileImage( + profileImage: viewModel.user?.profileImage, + ); - Widget _buildProfileName() => const Text( - 'Hi, Bisrat 👋', - style: TextStyle( - fontSize: 25, - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), + Widget _buildProfileName(ProfileViewModel viewModel) => Text( + 'Hi, ${viewModel.user?.firstName ?? ''} 👋', + style: style25DG600, ); Widget _buildViewProfileButton(ProfileViewModel viewModel) => diff --git a/lib/ui/views/profile/profile_viewmodel.dart b/lib/ui/views/profile/profile_viewmodel.dart index ec35e68..72f037b 100644 --- a/lib/ui/views/profile/profile_viewmodel.dart +++ b/lib/ui/views/profile/profile_viewmodel.dart @@ -3,6 +3,7 @@ import 'package:stacked_services/stacked_services.dart'; import 'package:yimaru_app/app/app.router.dart'; import '../../../app/app.locator.dart'; +import '../../../models/user_model.dart'; import '../../../services/authentication_service.dart'; class ProfileViewModel extends BaseViewModel { @@ -10,6 +11,10 @@ class ProfileViewModel extends BaseViewModel { final _authenticationService = locator(); + late final UserModel? _user = _authenticationService.user; + + UserModel? get user => _user; + Future logOut() async { await _authenticationService.logOut(); await _navigationService.replaceWithLoginView(); diff --git a/lib/ui/views/profile_detail/profile_detail_view.dart b/lib/ui/views/profile_detail/profile_detail_view.dart index 9fe7573..0f2ad5d 100644 --- a/lib/ui/views/profile_detail/profile_detail_view.dart +++ b/lib/ui/views/profile_detail/profile_detail_view.dart @@ -101,7 +101,7 @@ class ProfileDetailView extends StackedView List _buildColumnChildren(ProfileDetailViewModel viewModel) => [ verticalSpaceMedium, - _buildProfileImage(), + _buildProfileImageWrapper(viewModel), verticalSpaceMedium, _buildNameFormSection(viewModel), verticalSpaceMedium, @@ -120,9 +120,12 @@ class ProfileDetailView extends StackedView _buildLowerColumn(viewModel) ]; - Widget _buildProfileImage() => - const Align(alignment: Alignment.center, child: ProfileImage()); + Widget _buildProfileImageWrapper(ProfileDetailViewModel viewModel) => + Align(alignment: Alignment.center, child: _buildProfileImage(viewModel)); + Widget _buildProfileImage(ProfileDetailViewModel viewModel) => ProfileImage( + profileImage: viewModel.user?.profileImage, + ); Widget _buildNameFormSection(ProfileDetailViewModel viewModel) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: _buildNameFormChildren(viewModel), diff --git a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart index 16769e6..a98fa4d 100644 --- a/lib/ui/views/profile_detail/profile_detail_viewmodel.dart +++ b/lib/ui/views/profile_detail/profile_detail_viewmodel.dart @@ -2,9 +2,18 @@ import 'package:stacked/stacked.dart'; import 'package:stacked_services/stacked_services.dart'; import '../../../app/app.locator.dart'; +import '../../../models/user_model.dart'; +import '../../../services/authentication_service.dart'; class ProfileDetailViewModel extends FormViewModel { final _navigationService = locator(); + + final _authenticationService = locator(); + + late final UserModel? _user = _authenticationService.user; + + UserModel? get user => _user; + // First name bool _focusFirstName = false; diff --git a/lib/ui/views/register/register_viewmodel.dart b/lib/ui/views/register/register_viewmodel.dart index decc358..c25e2ef 100644 --- a/lib/ui/views/register/register_viewmodel.dart +++ b/lib/ui/views/register/register_viewmodel.dart @@ -246,7 +246,7 @@ class RegisterViewModel extends FormViewModel { // 'refreshToken': 'refreshToken' // } - await _authenticationService.saveUserData(data); + await _authenticationService.saveBasicUserData(data); showSuccessToast(response['message']); } else { showErrorToast(response['message']); diff --git a/lib/ui/widgets/large_app_bar.dart b/lib/ui/widgets/large_app_bar.dart index d3dc9c4..c395baf 100644 --- a/lib/ui/widgets/large_app_bar.dart +++ b/lib/ui/widgets/large_app_bar.dart @@ -6,11 +6,13 @@ class LargeAppBar extends StatelessWidget { final bool showBackButton; final GestureTapCallback? onPop; final bool showLanguageSelection; + final GestureTapCallback? onClose; final GestureTapCallback? onLanguage; const LargeAppBar( {super.key, this.onPop, + this.onClose, this.onLanguage, required this.showBackButton, required this.showLanguageSelection}); @@ -53,9 +55,9 @@ class LargeAppBar extends StatelessWidget { Widget _buildRightButton() => Align( alignment: Alignment.bottomRight, - child: showLanguageSelection ? _buildLanguageSelector() : Container() - // _buildCloseButton() - ); + child: showLanguageSelection + ? _buildLanguageSelector() + : _buildCloseButton()); Widget _buildLanguageSelector() => LanguageButton( language: 'EN', @@ -63,8 +65,9 @@ class LargeAppBar extends StatelessWidget { ); Widget _buildCloseButton() => IconButton( - onPressed: () {}, + onPressed: onClose, icon: _buildCloseIcon(), + padding: const EdgeInsets.only(top: 5), ); Widget _buildCloseIcon() => const Icon( diff --git a/lib/ui/widgets/learn_app_bar.dart b/lib/ui/widgets/learn_app_bar.dart index e3e331c..252cb63 100644 --- a/lib/ui/widgets/learn_app_bar.dart +++ b/lib/ui/widgets/learn_app_bar.dart @@ -1,10 +1,15 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart'; import '../common/app_colors.dart'; class LearnAppBar extends StatelessWidget { - const LearnAppBar({super.key}); + final String? name; + final String? profileImage; + + const LearnAppBar( + {super.key, required this.name, required this.profileImage}); @override Widget build(BuildContext context) => _buildStack(); @@ -30,9 +35,22 @@ class LearnAppBar extends StatelessWidget { List _buildProfileRowChildren() => [_buildProfileImage(), horizontalSpaceSmall, _buildGreetingTextColumn()]; - Widget _buildProfileImage() => const CircleAvatar( + Widget _buildProfileImage() => CircleAvatar( radius: 25, - backgroundImage: AssetImage('assets/images/profile.png'), + backgroundColor: kcPrimaryColor, + backgroundImage: profileImage != null + ? CachedNetworkImageProvider(profileImage!) + : null, + child: _buildImageBuilder(), + ); + + Widget? _buildImageBuilder() => + profileImage == null ? _buildPersonIcon() : null; + + Widget _buildPersonIcon() => const Icon( + Icons.person, + size: 30, + color: kcWhite, ); Widget _buildGreetingTextColumn() => Column( @@ -44,28 +62,19 @@ class LearnAppBar extends StatelessWidget { List _buildGreetingChildren() => [_buildGreetingTitle(), _buildSubTitle()]; - Widget _buildGreetingTitle() => const Text.rich( - TextSpan( - text: 'Hello,', - style: TextStyle( - color: kcDarkGrey, - fontWeight: FontWeight.w600, - ), - children: [ - TextSpan( - text: ' Bisrat!', - style: TextStyle( - color: kcPrimaryColor, - fontWeight: FontWeight.w600, - ), - ) - ]), + Widget _buildGreetingTitle() => Text.rich( + TextSpan(text: 'Hello,', style: style14DG600, children: [ + TextSpan( + text: ' $name!', + style: style14P600, + ) + ]), ); - Widget _buildSubTitle() => const Text( + Widget _buildSubTitle() => Text( 'Ready to keep learning English today?', textAlign: TextAlign.center, - style: TextStyle(color: kcMediumGrey), + style: style14DG400, ); Widget _buildNotificationIconWrapper() => diff --git a/lib/ui/widgets/learn_module_tile.dart b/lib/ui/widgets/learn_module_tile.dart index 4160154..478e261 100644 --- a/lib/ui/widgets/learn_module_tile.dart +++ b/lib/ui/widgets/learn_module_tile.dart @@ -192,7 +192,7 @@ class LearnModuleTile extends ViewModelWidget { ); Widget _buildLessonButton(LearnModuleViewModel viewModel) => - CustomElevatedButton( + CustomElevatedButton( height: 15, borderRadius: 12, text: 'View Lessons', diff --git a/lib/ui/widgets/profile_image.dart b/lib/ui/widgets/profile_image.dart index 42f61f3..7453547 100644 --- a/lib/ui/widgets/profile_image.dart +++ b/lib/ui/widgets/profile_image.dart @@ -1,8 +1,10 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:yimaru_app/ui/common/app_colors.dart'; class ProfileImage extends StatelessWidget { - const ProfileImage({super.key}); + final String? profileImage; + const ProfileImage({super.key, required this.profileImage}); @override Widget build(BuildContext context) => _buildSizedBox(); @@ -22,9 +24,22 @@ class ProfileImage extends StatelessWidget { child: _buildProfileImage(), ); - Widget _buildProfileImage() => const CircleAvatar( + Widget _buildProfileImage() => CircleAvatar( radius: 50, - backgroundImage: AssetImage('assets/images/profile.png'), + backgroundColor: kcPrimaryColor, + backgroundImage: profileImage != null + ? CachedNetworkImageProvider(profileImage!) + : null, + child: _buildImageBuilder(), + ); + + Widget? _buildImageBuilder() => + profileImage == null ? _buildPersonIcon() : null; + + Widget _buildPersonIcon() => const Icon( + Icons.person, + size: 50, + color: kcWhite, ); Widget _buildCameraButtonWrapper() => Align( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index a1d1dac..a9f61f7 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,11 @@ import Foundation import battery_plus import connectivity_plus import flutter_secure_storage_darwin +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index cfad602..29c3534 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.12.3" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -318,6 +342,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_html: dependency: "direct main" description: @@ -704,6 +736,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.2.3" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" omni_datetime_picker: dependency: "direct main" description: @@ -917,6 +957,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -989,6 +1069,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 8bb6287..8f9fb5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,8 +27,10 @@ dependencies: stacked_services: ^1.1.0 omni_datetime_picker: any json_serializable: ^6.8.0 + cached_network_image: ^3.4.1 flutter_secure_storage: ^10.0.0 flutter_timer_countdown: ^1.0.7 + internet_connection_checker_plus: ^2.9.1+2 dev_dependencies: diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart deleted file mode 100644 index df3b057..0000000 --- a/test/helpers/test_helpers.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:yimaru_app/app/app.locator.dart'; -import 'package:stacked_services/stacked_services.dart'; -// @stacked-import - -import 'test_helpers.mocks.dart'; - -@GenerateMocks( - [], - customMocks: [ - MockSpec(onMissingStub: OnMissingStub.returnDefault), - MockSpec(onMissingStub: OnMissingStub.returnDefault), - MockSpec(onMissingStub: OnMissingStub.returnDefault), - // @stacked-mock-spec - ], -) -void registerServices() { - getAndRegisterNavigationService(); - getAndRegisterBottomSheetService(); - getAndRegisterDialogService(); - // @stacked-mock-register -} - -MockNavigationService getAndRegisterNavigationService() { - _removeRegistrationIfExists(); - final service = MockNavigationService(); - locator.registerSingleton(service); - return service; -} - -MockBottomSheetService getAndRegisterBottomSheetService({ - SheetResponse? showCustomSheetResponse, -}) { - _removeRegistrationIfExists(); - final service = MockBottomSheetService(); - - when( - service.showCustomSheet( - enableDrag: anyNamed('enableDrag'), - enterBottomSheetDuration: anyNamed('enterBottomSheetDuration'), - exitBottomSheetDuration: anyNamed('exitBottomSheetDuration'), - ignoreSafeArea: anyNamed('ignoreSafeArea'), - isScrollControlled: anyNamed('isScrollControlled'), - barrierDismissible: anyNamed('barrierDismissible'), - additionalButtonTitle: anyNamed('additionalButtonTitle'), - variant: anyNamed('variant'), - title: anyNamed('title'), - hasImage: anyNamed('hasImage'), - imageUrl: anyNamed('imageUrl'), - showIconInMainButton: anyNamed('showIconInMainButton'), - mainButtonTitle: anyNamed('mainButtonTitle'), - showIconInSecondaryButton: anyNamed('showIconInSecondaryButton'), - secondaryButtonTitle: anyNamed('secondaryButtonTitle'), - showIconInAdditionalButton: anyNamed('showIconInAdditionalButton'), - takesInput: anyNamed('takesInput'), - barrierColor: anyNamed('barrierColor'), - barrierLabel: anyNamed('barrierLabel'), - customData: anyNamed('customData'), - data: anyNamed('data'), - description: anyNamed('description'), - ), - ).thenAnswer( - (realInvocation) => - Future.value(showCustomSheetResponse ?? SheetResponse()), - ); - - locator.registerSingleton(service); - return service; -} - -MockDialogService getAndRegisterDialogService() { - _removeRegistrationIfExists(); - final service = MockDialogService(); - locator.registerSingleton(service); - return service; -} - -// @stacked-mock-create - -void _removeRegistrationIfExists() { - if (locator.isRegistered()) { - locator.unregister(); - } -} diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart deleted file mode 100644 index 651298f..0000000 --- a/test/helpers/test_helpers.mocks.dart +++ /dev/null @@ -1,684 +0,0 @@ -// Mocks generated by Mockito 5.4.4 from annotations -// in yimaru_app/test/helpers/test_helpers.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i5; -import 'dart:ui' as _i6; - -import 'package:flutter/material.dart' as _i4; -import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i3; -import 'package:stacked_services/stacked_services.dart' as _i2; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class - -/// A class which mocks [NavigationService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockNavigationService extends _i1.Mock implements _i2.NavigationService { - @override - String get previousRoute => (super.noSuchMethod( - Invocation.getter(#previousRoute), - returnValue: _i3.dummyValue( - this, - Invocation.getter(#previousRoute), - ), - returnValueForMissingStub: _i3.dummyValue( - this, - Invocation.getter(#previousRoute), - ), - ) as String); - - @override - String get currentRoute => (super.noSuchMethod( - Invocation.getter(#currentRoute), - returnValue: _i3.dummyValue( - this, - Invocation.getter(#currentRoute), - ), - returnValueForMissingStub: _i3.dummyValue( - this, - Invocation.getter(#currentRoute), - ), - ) as String); - - @override - _i4.GlobalKey<_i4.NavigatorState>? nestedNavigationKey(int? index) => - (super.noSuchMethod( - Invocation.method( - #nestedNavigationKey, - [index], - ), - returnValueForMissingStub: null, - ) as _i4.GlobalKey<_i4.NavigatorState>?); - - @override - void config({ - bool? enableLog, - bool? defaultPopGesture, - bool? defaultOpaqueRoute, - Duration? defaultDurationTransition, - bool? defaultGlobalState, - _i2.Transition? defaultTransitionStyle, - String? defaultTransition, - }) => - super.noSuchMethod( - Invocation.method( - #config, - [], - { - #enableLog: enableLog, - #defaultPopGesture: defaultPopGesture, - #defaultOpaqueRoute: defaultOpaqueRoute, - #defaultDurationTransition: defaultDurationTransition, - #defaultGlobalState: defaultGlobalState, - #defaultTransitionStyle: defaultTransitionStyle, - #defaultTransition: defaultTransition, - }, - ), - returnValueForMissingStub: null, - ); - - @override - _i5.Future? navigateWithTransition( - _i4.Widget? page, { - bool? opaque, - String? transition = r'', - Duration? duration, - bool? popGesture, - int? id, - _i4.Curve? curve, - bool? fullscreenDialog = false, - bool? preventDuplicates = true, - _i2.Transition? transitionClass, - _i2.Transition? transitionStyle, - String? routeName, - }) => - (super.noSuchMethod( - Invocation.method( - #navigateWithTransition, - [page], - { - #opaque: opaque, - #transition: transition, - #duration: duration, - #popGesture: popGesture, - #id: id, - #curve: curve, - #fullscreenDialog: fullscreenDialog, - #preventDuplicates: preventDuplicates, - #transitionClass: transitionClass, - #transitionStyle: transitionStyle, - #routeName: routeName, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? replaceWithTransition( - _i4.Widget? page, { - bool? opaque, - String? transition = r'', - Duration? duration, - bool? popGesture, - int? id, - _i4.Curve? curve, - bool? fullscreenDialog = false, - bool? preventDuplicates = true, - _i2.Transition? transitionClass, - _i2.Transition? transitionStyle, - String? routeName, - }) => - (super.noSuchMethod( - Invocation.method( - #replaceWithTransition, - [page], - { - #opaque: opaque, - #transition: transition, - #duration: duration, - #popGesture: popGesture, - #id: id, - #curve: curve, - #fullscreenDialog: fullscreenDialog, - #preventDuplicates: preventDuplicates, - #transitionClass: transitionClass, - #transitionStyle: transitionStyle, - #routeName: routeName, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - bool back({ - dynamic result, - int? id, - }) => - (super.noSuchMethod( - Invocation.method( - #back, - [], - { - #result: result, - #id: id, - }, - ), - returnValue: false, - returnValueForMissingStub: false, - ) as bool); - - @override - void popUntil( - _i4.RoutePredicate? predicate, { - int? id, - }) => - super.noSuchMethod( - Invocation.method( - #popUntil, - [predicate], - {#id: id}, - ), - returnValueForMissingStub: null, - ); - - @override - void popRepeated(int? popTimes) => super.noSuchMethod( - Invocation.method( - #popRepeated, - [popTimes], - ), - returnValueForMissingStub: null, - ); - - @override - _i5.Future? navigateTo( - String? routeName, { - dynamic arguments, - int? id, - bool? preventDuplicates = true, - Map? parameters, - _i4.RouteTransitionsBuilder? transition, - }) => - (super.noSuchMethod( - Invocation.method( - #navigateTo, - [routeName], - { - #arguments: arguments, - #id: id, - #preventDuplicates: preventDuplicates, - #parameters: parameters, - #transition: transition, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? navigateToView( - _i4.Widget? view, { - dynamic arguments, - int? id, - bool? opaque, - _i4.Curve? curve, - Duration? duration, - bool? fullscreenDialog = false, - bool? popGesture, - bool? preventDuplicates = true, - _i2.Transition? transition, - _i2.Transition? transitionStyle, - }) => - (super.noSuchMethod( - Invocation.method( - #navigateToView, - [view], - { - #arguments: arguments, - #id: id, - #opaque: opaque, - #curve: curve, - #duration: duration, - #fullscreenDialog: fullscreenDialog, - #popGesture: popGesture, - #preventDuplicates: preventDuplicates, - #transition: transition, - #transitionStyle: transitionStyle, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? replaceWith( - String? routeName, { - dynamic arguments, - int? id, - bool? preventDuplicates = true, - Map? parameters, - _i4.RouteTransitionsBuilder? transition, - }) => - (super.noSuchMethod( - Invocation.method( - #replaceWith, - [routeName], - { - #arguments: arguments, - #id: id, - #preventDuplicates: preventDuplicates, - #parameters: parameters, - #transition: transition, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? clearStackAndShow( - String? routeName, { - dynamic arguments, - int? id, - Map? parameters, - }) => - (super.noSuchMethod( - Invocation.method( - #clearStackAndShow, - [routeName], - { - #arguments: arguments, - #id: id, - #parameters: parameters, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? clearStackAndShowView( - _i4.Widget? view, { - dynamic arguments, - int? id, - }) => - (super.noSuchMethod( - Invocation.method( - #clearStackAndShowView, - [view], - { - #arguments: arguments, - #id: id, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? clearTillFirstAndShow( - String? routeName, { - dynamic arguments, - int? id, - bool? preventDuplicates = true, - Map? parameters, - }) => - (super.noSuchMethod( - Invocation.method( - #clearTillFirstAndShow, - [routeName], - { - #arguments: arguments, - #id: id, - #preventDuplicates: preventDuplicates, - #parameters: parameters, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? clearTillFirstAndShowView( - _i4.Widget? view, { - dynamic arguments, - int? id, - }) => - (super.noSuchMethod( - Invocation.method( - #clearTillFirstAndShowView, - [view], - { - #arguments: arguments, - #id: id, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); - - @override - _i5.Future? pushNamedAndRemoveUntil( - String? routeName, { - _i4.RoutePredicate? predicate, - dynamic arguments, - int? id, - }) => - (super.noSuchMethod( - Invocation.method( - #pushNamedAndRemoveUntil, - [routeName], - { - #predicate: predicate, - #arguments: arguments, - #id: id, - }, - ), - returnValueForMissingStub: null, - ) as _i5.Future?); -} - -/// A class which mocks [BottomSheetService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockBottomSheetService extends _i1.Mock - implements _i2.BottomSheetService { - @override - void setCustomSheetBuilders(Map? builders) => - super.noSuchMethod( - Invocation.method( - #setCustomSheetBuilders, - [builders], - ), - returnValueForMissingStub: null, - ); - - @override - _i5.Future<_i2.SheetResponse?> showBottomSheet({ - required String? title, - String? description, - String? confirmButtonTitle = r'Ok', - String? cancelButtonTitle, - bool? enableDrag = true, - bool? barrierDismissible = true, - bool? isScrollControlled = false, - Duration? exitBottomSheetDuration, - Duration? enterBottomSheetDuration, - bool? ignoreSafeArea, - bool? useRootNavigator = false, - double? elevation = 1.0, - }) => - (super.noSuchMethod( - Invocation.method( - #showBottomSheet, - [], - { - #title: title, - #description: description, - #confirmButtonTitle: confirmButtonTitle, - #cancelButtonTitle: cancelButtonTitle, - #enableDrag: enableDrag, - #barrierDismissible: barrierDismissible, - #isScrollControlled: isScrollControlled, - #exitBottomSheetDuration: exitBottomSheetDuration, - #enterBottomSheetDuration: enterBottomSheetDuration, - #ignoreSafeArea: ignoreSafeArea, - #useRootNavigator: useRootNavigator, - #elevation: elevation, - }, - ), - returnValue: _i5.Future<_i2.SheetResponse?>.value(), - returnValueForMissingStub: - _i5.Future<_i2.SheetResponse?>.value(), - ) as _i5.Future<_i2.SheetResponse?>); - - @override - _i5.Future<_i2.SheetResponse?> showCustomSheet({ - dynamic variant, - String? title, - String? description, - bool? hasImage = false, - String? imageUrl, - bool? showIconInMainButton = false, - String? mainButtonTitle, - bool? showIconInSecondaryButton = false, - String? secondaryButtonTitle, - bool? showIconInAdditionalButton = false, - String? additionalButtonTitle, - bool? takesInput = false, - _i6.Color? barrierColor = const _i6.Color(2315255808), - double? elevation = 1.0, - bool? barrierDismissible = true, - bool? isScrollControlled = false, - String? barrierLabel = r'', - dynamic customData, - R? data, - bool? enableDrag = true, - Duration? exitBottomSheetDuration, - Duration? enterBottomSheetDuration, - bool? ignoreSafeArea, - bool? useRootNavigator = false, - }) => - (super.noSuchMethod( - Invocation.method( - #showCustomSheet, - [], - { - #variant: variant, - #title: title, - #description: description, - #hasImage: hasImage, - #imageUrl: imageUrl, - #showIconInMainButton: showIconInMainButton, - #mainButtonTitle: mainButtonTitle, - #showIconInSecondaryButton: showIconInSecondaryButton, - #secondaryButtonTitle: secondaryButtonTitle, - #showIconInAdditionalButton: showIconInAdditionalButton, - #additionalButtonTitle: additionalButtonTitle, - #takesInput: takesInput, - #barrierColor: barrierColor, - #elevation: elevation, - #barrierDismissible: barrierDismissible, - #isScrollControlled: isScrollControlled, - #barrierLabel: barrierLabel, - #customData: customData, - #data: data, - #enableDrag: enableDrag, - #exitBottomSheetDuration: exitBottomSheetDuration, - #enterBottomSheetDuration: enterBottomSheetDuration, - #ignoreSafeArea: ignoreSafeArea, - #useRootNavigator: useRootNavigator, - }, - ), - returnValue: _i5.Future<_i2.SheetResponse?>.value(), - returnValueForMissingStub: _i5.Future<_i2.SheetResponse?>.value(), - ) as _i5.Future<_i2.SheetResponse?>); - - @override - void completeSheet(_i2.SheetResponse? response) => - super.noSuchMethod( - Invocation.method( - #completeSheet, - [response], - ), - returnValueForMissingStub: null, - ); -} - -/// A class which mocks [DialogService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockDialogService extends _i1.Mock implements _i2.DialogService { - @override - void registerCustomDialogBuilders( - Map? builders) => - super.noSuchMethod( - Invocation.method( - #registerCustomDialogBuilders, - [builders], - ), - returnValueForMissingStub: null, - ); - - @override - void registerCustomDialogBuilder({ - required dynamic variant, - required _i4.Widget Function( - _i4.BuildContext, - _i2.DialogRequest, - dynamic Function(_i2.DialogResponse), - )? builder, - }) => - super.noSuchMethod( - Invocation.method( - #registerCustomDialogBuilder, - [], - { - #variant: variant, - #builder: builder, - }, - ), - returnValueForMissingStub: null, - ); - - @override - _i5.Future<_i2.DialogResponse?> showDialog({ - String? title, - String? description, - String? cancelTitle, - _i6.Color? cancelTitleColor, - String? buttonTitle = r'Ok', - _i6.Color? buttonTitleColor, - bool? barrierDismissible = false, - _i4.RouteSettings? routeSettings, - _i4.GlobalKey<_i4.NavigatorState>? navigatorKey, - _i2.DialogPlatform? dialogPlatform, - }) => - (super.noSuchMethod( - Invocation.method( - #showDialog, - [], - { - #title: title, - #description: description, - #cancelTitle: cancelTitle, - #cancelTitleColor: cancelTitleColor, - #buttonTitle: buttonTitle, - #buttonTitleColor: buttonTitleColor, - #barrierDismissible: barrierDismissible, - #routeSettings: routeSettings, - #navigatorKey: navigatorKey, - #dialogPlatform: dialogPlatform, - }, - ), - returnValue: _i5.Future<_i2.DialogResponse?>.value(), - returnValueForMissingStub: - _i5.Future<_i2.DialogResponse?>.value(), - ) as _i5.Future<_i2.DialogResponse?>); - - @override - _i5.Future<_i2.DialogResponse?> showCustomDialog({ - dynamic variant, - String? title, - String? description, - bool? hasImage = false, - String? imageUrl, - bool? showIconInMainButton = false, - String? mainButtonTitle, - bool? showIconInSecondaryButton = false, - String? secondaryButtonTitle, - bool? showIconInAdditionalButton = false, - String? additionalButtonTitle, - bool? takesInput = false, - _i6.Color? barrierColor = const _i6.Color(2315255808), - bool? barrierDismissible = false, - String? barrierLabel = r'', - bool? useSafeArea = true, - _i4.RouteSettings? routeSettings, - _i4.GlobalKey<_i4.NavigatorState>? navigatorKey, - _i4.RouteTransitionsBuilder? transitionBuilder, - dynamic customData, - R? data, - }) => - (super.noSuchMethod( - Invocation.method( - #showCustomDialog, - [], - { - #variant: variant, - #title: title, - #description: description, - #hasImage: hasImage, - #imageUrl: imageUrl, - #showIconInMainButton: showIconInMainButton, - #mainButtonTitle: mainButtonTitle, - #showIconInSecondaryButton: showIconInSecondaryButton, - #secondaryButtonTitle: secondaryButtonTitle, - #showIconInAdditionalButton: showIconInAdditionalButton, - #additionalButtonTitle: additionalButtonTitle, - #takesInput: takesInput, - #barrierColor: barrierColor, - #barrierDismissible: barrierDismissible, - #barrierLabel: barrierLabel, - #useSafeArea: useSafeArea, - #routeSettings: routeSettings, - #navigatorKey: navigatorKey, - #transitionBuilder: transitionBuilder, - #customData: customData, - #data: data, - }, - ), - returnValue: _i5.Future<_i2.DialogResponse?>.value(), - returnValueForMissingStub: _i5.Future<_i2.DialogResponse?>.value(), - ) as _i5.Future<_i2.DialogResponse?>); - - @override - _i5.Future<_i2.DialogResponse?> showConfirmationDialog({ - String? title, - String? description, - String? cancelTitle = r'Cancel', - _i6.Color? cancelTitleColor, - String? confirmationTitle = r'Ok', - _i6.Color? confirmationTitleColor, - bool? barrierDismissible = false, - _i4.RouteSettings? routeSettings, - _i2.DialogPlatform? dialogPlatform, - }) => - (super.noSuchMethod( - Invocation.method( - #showConfirmationDialog, - [], - { - #title: title, - #description: description, - #cancelTitle: cancelTitle, - #cancelTitleColor: cancelTitleColor, - #confirmationTitle: confirmationTitle, - #confirmationTitleColor: confirmationTitleColor, - #barrierDismissible: barrierDismissible, - #routeSettings: routeSettings, - #dialogPlatform: dialogPlatform, - }, - ), - returnValue: _i5.Future<_i2.DialogResponse?>.value(), - returnValueForMissingStub: - _i5.Future<_i2.DialogResponse?>.value(), - ) as _i5.Future<_i2.DialogResponse?>); - - @override - void completeDialog(_i2.DialogResponse? response) => - super.noSuchMethod( - Invocation.method( - #completeDialog, - [response], - ), - returnValueForMissingStub: null, - ); -} diff --git a/test/services/image_picker_service_test.dart b/test/services/image_picker_service_test.dart new file mode 100644 index 0000000..7e0b6da --- /dev/null +++ b/test/services/image_picker_service_test.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:yimaru_app/app/app.locator.dart'; + +import '../helpers/test_helpers.dart'; + +void main() { + group('ImagePickerServiceTest -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +} diff --git a/test/viewmodels/failure_viewmodel_test.dart b/test/viewmodels/failure_viewmodel_test.dart new file mode 100644 index 0000000..d06419f --- /dev/null +++ b/test/viewmodels/failure_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('FailureViewModel Tests -', () { + setUp(() => registerServices()); + tearDown(() => locator.reset()); + }); +}