first commit

This commit is contained in:
BisratHailu 2026-01-23 09:14:07 +03:00
parent befbfb4727
commit 8f329f774d
44 changed files with 704 additions and 956 deletions

View File

@ -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/welcome/welcome_view.dart';
import 'package:yimaru_app/ui/views/assessment/assessment_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/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 // @stacked-import
@StackedApp( @StackedApp(
@ -57,6 +59,7 @@ import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart';
MaterialRoute(page: WelcomeView), MaterialRoute(page: WelcomeView),
MaterialRoute(page: AssessmentView), MaterialRoute(page: AssessmentView),
MaterialRoute(page: LearnLessonView), MaterialRoute(page: LearnLessonView),
MaterialRoute(page: FailureView),
// @stacked-route // @stacked-route
], ],
dependencies: [ dependencies: [
@ -68,6 +71,7 @@ import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart';
LazySingleton(classType: SecureStorageService), LazySingleton(classType: SecureStorageService),
LazySingleton(classType: DioService), LazySingleton(classType: DioService),
LazySingleton(classType: StatusCheckerService), LazySingleton(classType: StatusCheckerService),
LazySingleton(classType: ImagePickerService),
// @stacked-service // @stacked-service
], ],
bottomsheets: [ bottomsheets: [

View File

@ -14,6 +14,7 @@ import 'package:stacked_shared/stacked_shared.dart';
import '../services/api_service.dart'; import '../services/api_service.dart';
import '../services/authentication_service.dart'; import '../services/authentication_service.dart';
import '../services/dio_service.dart'; import '../services/dio_service.dart';
import '../services/image_picker_service.dart';
import '../services/secure_storage_service.dart'; import '../services/secure_storage_service.dart';
import '../services/status_checker_service.dart'; import '../services/status_checker_service.dart';
@ -36,4 +37,5 @@ Future<void> setupLocator({
locator.registerLazySingleton(() => SecureStorageService()); locator.registerLazySingleton(() => SecureStorageService());
locator.registerLazySingleton(() => DioService()); locator.registerLazySingleton(() => DioService());
locator.registerLazySingleton(() => StatusCheckerService()); locator.registerLazySingleton(() => StatusCheckerService());
locator.registerLazySingleton(() => ImagePickerService());
} }

View File

@ -5,16 +5,17 @@
// ************************************************************************** // **************************************************************************
// ignore_for_file: no_leading_underscores_for_library_prefixes // 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:flutter/material.dart';
import 'package:stacked/stacked.dart' as _i1; 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' import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
as _i10; as _i10;
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23; import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23;
import 'package:yimaru_app/ui/views/call_support/call_support_view.dart' import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
as _i13; as _i13;
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7; 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/home/home_view.dart' as _i2;
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i14; 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/learn_view.dart' as _i19;
@ -89,6 +90,8 @@ class Routes {
static const learnLessonView = '/learn-lesson-view'; static const learnLessonView = '/learn-lesson-view';
static const failureView = '/failure-view';
static const all = <String>{ static const all = <String>{
homeView, homeView,
onboardingView, onboardingView,
@ -113,6 +116,7 @@ class Routes {
welcomeView, welcomeView,
assessmentView, assessmentView,
learnLessonView, learnLessonView,
failureView,
}; };
} }
@ -210,17 +214,21 @@ class StackedRouter extends _i1.RouterBase {
Routes.learnLessonView, Routes.learnLessonView,
page: _i24.LearnLessonView, page: _i24.LearnLessonView,
), ),
_i1.RouteDef(
Routes.failureView,
page: _i25.FailureView,
),
]; ];
final _pagesMap = <Type, _i1.StackedRouteFactory>{ final _pagesMap = <Type, _i1.StackedRouteFactory>{
_i2.HomeView: (data) { _i2.HomeView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i2.HomeView(), builder: (context) => const _i2.HomeView(),
settings: data, settings: data,
); );
}, },
_i3.OnboardingView: (data) { _i3.OnboardingView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i3.OnboardingView(), builder: (context) => const _i3.OnboardingView(),
settings: data, settings: data,
); );
@ -229,133 +237,141 @@ class StackedRouter extends _i1.RouterBase {
final args = data.getArgs<StartupViewArguments>( final args = data.getArgs<StartupViewArguments>(
orElse: () => const StartupViewArguments(), orElse: () => const StartupViewArguments(),
); );
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => _i4.StartupView(key: args.key, label: args.label), builder: (context) => _i4.StartupView(key: args.key, label: args.label),
settings: data, settings: data,
); );
}, },
_i5.ProfileView: (data) { _i5.ProfileView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i5.ProfileView(), builder: (context) => const _i5.ProfileView(),
settings: data, settings: data,
); );
}, },
_i6.ProfileDetailView: (data) { _i6.ProfileDetailView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i6.ProfileDetailView(), builder: (context) => const _i6.ProfileDetailView(),
settings: data, settings: data,
); );
}, },
_i7.DownloadsView: (data) { _i7.DownloadsView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i7.DownloadsView(), builder: (context) => const _i7.DownloadsView(),
settings: data, settings: data,
); );
}, },
_i8.ProgressView: (data) { _i8.ProgressView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i8.ProgressView(), builder: (context) => const _i8.ProgressView(),
settings: data, settings: data,
); );
}, },
_i9.OngoingProgressView: (data) { _i9.OngoingProgressView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i9.OngoingProgressView(), builder: (context) => const _i9.OngoingProgressView(),
settings: data, settings: data,
); );
}, },
_i10.AccountPrivacyView: (data) { _i10.AccountPrivacyView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i10.AccountPrivacyView(), builder: (context) => const _i10.AccountPrivacyView(),
settings: data, settings: data,
); );
}, },
_i11.SupportView: (data) { _i11.SupportView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i11.SupportView(), builder: (context) => const _i11.SupportView(),
settings: data, settings: data,
); );
}, },
_i12.TelegramSupportView: (data) { _i12.TelegramSupportView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i12.TelegramSupportView(), builder: (context) => const _i12.TelegramSupportView(),
settings: data, settings: data,
); );
}, },
_i13.CallSupportView: (data) { _i13.CallSupportView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i13.CallSupportView(), builder: (context) => const _i13.CallSupportView(),
settings: data, settings: data,
); );
}, },
_i14.LanguageView: (data) { _i14.LanguageView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i14.LanguageView(), builder: (context) => const _i14.LanguageView(),
settings: data, settings: data,
); );
}, },
_i15.PrivacyPolicyView: (data) { _i15.PrivacyPolicyView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i15.PrivacyPolicyView(), builder: (context) => const _i15.PrivacyPolicyView(),
settings: data, settings: data,
); );
}, },
_i16.TermsAndConditionsView: (data) { _i16.TermsAndConditionsView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i16.TermsAndConditionsView(), builder: (context) => const _i16.TermsAndConditionsView(),
settings: data, settings: data,
); );
}, },
_i17.RegisterView: (data) { _i17.RegisterView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i17.RegisterView(), builder: (context) => const _i17.RegisterView(),
settings: data, settings: data,
); );
}, },
_i18.LoginView: (data) { _i18.LoginView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i18.LoginView(), builder: (context) => const _i18.LoginView(),
settings: data, settings: data,
); );
}, },
_i19.LearnView: (data) { _i19.LearnView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i19.LearnView(), builder: (context) => const _i19.LearnView(),
settings: data, settings: data,
); );
}, },
_i20.LearnLevelView: (data) { _i20.LearnLevelView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i20.LearnLevelView(), builder: (context) => const _i20.LearnLevelView(),
settings: data, settings: data,
); );
}, },
_i21.LearnModuleView: (data) { _i21.LearnModuleView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i21.LearnModuleView(), builder: (context) => const _i21.LearnModuleView(),
settings: data, settings: data,
); );
}, },
_i22.WelcomeView: (data) { _i22.WelcomeView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i22.WelcomeView(), builder: (context) => const _i22.WelcomeView(),
settings: data, settings: data,
); );
}, },
_i23.AssessmentView: (data) { _i23.AssessmentView: (data) {
final args = data.getArgs<AssessmentViewArguments>(nullOk: false); final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => builder: (context) =>
_i23.AssessmentView(key: args.key, data: args.data), _i23.AssessmentView(key: args.key, data: args.data),
settings: data, settings: data,
); );
}, },
_i24.LearnLessonView: (data) { _i24.LearnLessonView: (data) {
return _i25.MaterialPageRoute<dynamic>( return _i26.MaterialPageRoute<dynamic>(
builder: (context) => const _i24.LearnLessonView(), builder: (context) => const _i24.LearnLessonView(),
settings: data, settings: data,
); );
}, },
_i25.FailureView: (data) {
final args = data.getArgs<FailureViewArguments>(nullOk: false);
return _i26.MaterialPageRoute<dynamic>(
builder: (context) =>
_i25.FailureView(key: args.key, label: args.label),
settings: data,
);
},
}; };
@override @override
@ -371,7 +387,7 @@ class StartupViewArguments {
this.label = 'Loading', this.label = 'Loading',
}); });
final _i25.Key? key; final _i26.Key? key;
final String label; final String label;
@ -398,7 +414,7 @@ class AssessmentViewArguments {
required this.data, required this.data,
}); });
final _i25.Key? key; final _i26.Key? key;
final Map<String, dynamic> data; final Map<String, dynamic> 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<dynamic> navigateToHomeView([ Future<dynamic> navigateToHomeView([
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -449,7 +492,7 @@ extension NavigatorStateExtension on _i26.NavigationService {
} }
Future<dynamic> navigateToStartupView({ Future<dynamic> navigateToStartupView({
_i25.Key? key, _i26.Key? key,
String label = 'Loading', String label = 'Loading',
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -718,7 +761,7 @@ extension NavigatorStateExtension on _i26.NavigationService {
} }
Future<dynamic> navigateToAssessmentView({ Future<dynamic> navigateToAssessmentView({
_i25.Key? key, _i26.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -748,6 +791,23 @@ extension NavigatorStateExtension on _i26.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToFailureView({
_i26.Key? key,
required String label,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return navigateTo<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
Future<dynamic> replaceWithHomeView([ Future<dynamic> replaceWithHomeView([
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -777,7 +837,7 @@ extension NavigatorStateExtension on _i26.NavigationService {
} }
Future<dynamic> replaceWithStartupView({ Future<dynamic> replaceWithStartupView({
_i25.Key? key, _i26.Key? key,
String label = 'Loading', String label = 'Loading',
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -1046,7 +1106,7 @@ extension NavigatorStateExtension on _i26.NavigationService {
} }
Future<dynamic> replaceWithAssessmentView({ Future<dynamic> replaceWithAssessmentView({
_i25.Key? key, _i26.Key? key,
required Map<String, dynamic> data, required Map<String, dynamic> data,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
@ -1075,4 +1135,21 @@ extension NavigatorStateExtension on _i26.NavigationService {
parameters: parameters, parameters: parameters,
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithFailureView({
_i26.Key? key,
required String label,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition,
}) async {
return replaceWith<dynamic>(Routes.failureView,
arguments: FailureViewArguments(key: key, label: label),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
transition: transition);
}
} }

View File

@ -4,9 +4,13 @@ part 'user_model.g.dart';
@JsonSerializable() @JsonSerializable()
class UserModel { class UserModel {
final String? firstName;
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id')
final int? userId; final int? userId;
final String? profileImage;
final bool? profileCompleted; final bool? profileCompleted;
@JsonKey(name: 'access_token') @JsonKey(name: 'access_token')
@ -15,11 +19,14 @@ class UserModel {
@JsonKey(name: 'refresh_token') @JsonKey(name: 'refresh_token')
final String? refreshToken; final String? refreshToken;
const UserModel( const UserModel({
{this.userId, this.userId,
this.accessToken, this.firstName,
this.profileCompleted, this.accessToken,
this.refreshToken}); this.profileImage,
this.refreshToken,
this.profileCompleted,
});
factory UserModel.fromJson(Map<String, dynamic> json) => factory UserModel.fromJson(Map<String, dynamic> json) =>
_$UserModelFromJson(json); _$UserModelFromJson(json);

View File

@ -8,13 +8,17 @@ part of 'user_model.dart';
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel( UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
userId: (json['user_id'] as num?)?.toInt(), userId: (json['user_id'] as num?)?.toInt(),
firstName: json['firstName'] as String?,
accessToken: json['access_token'] as String?, accessToken: json['access_token'] as String?,
profileCompleted: json['profileCompleted'] as bool?, profileImage: json['profileImage'] as String?,
refreshToken: json['refresh_token'] as String?, refreshToken: json['refresh_token'] as String?,
profileCompleted: json['profileCompleted'] as bool?,
); );
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
'firstName': instance.firstName,
'user_id': instance.userId, 'user_id': instance.userId,
'profileImage': instance.profileImage,
'profileCompleted': instance.profileCompleted, 'profileCompleted': instance.profileCompleted,
'access_token': instance.accessToken, 'access_token': instance.accessToken,
'refresh_token': instance.refreshToken, 'refresh_token': instance.refreshToken,

View File

@ -14,7 +14,7 @@ class ApiService {
Future<Map<String, dynamic>> register(Map<String, dynamic> data) async { Future<Map<String, dynamic>> register(Map<String, dynamic> data) async {
try { try {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kRegisterUrl', '$baseUrl/$kUserUrl/$kRegisterUrl',
data: data, data: data,
); );
@ -69,7 +69,7 @@ class ApiService {
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async { Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
try { try {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kVerifyOtpUrl', '$baseUrl/$kUserUrl/$kVerifyOtpUrl',
data: data, data: data,
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -96,7 +96,7 @@ class ApiService {
Future<Map<String, dynamic>> resendOtp(Map<String, dynamic> data) async { Future<Map<String, dynamic>> resendOtp(Map<String, dynamic> data) async {
try { try {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$baseUrl/$userUrl/$kResendOtpUrl', '$baseUrl/$kUserUrl/$kResendOtpUrl',
data: data, data: data,
); );
@ -123,7 +123,7 @@ class ApiService {
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async { Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
try { try {
Response response = await _service.dio.get( Response response = await _service.dio.get(
'$baseUrl/$userUrl/${user?.userId}/$kProfileStatusUrl', '$baseUrl/$kUserUrl/${user?.userId}/$kProfileStatusUrl',
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -146,18 +146,42 @@ class ApiService {
} }
} }
// Get profile
Future<Map<String, dynamic>> 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 // Update profile
Future<Map<String, dynamic>> updateProfile( Future<Map<String, dynamic>> updateProfile(
{required UserModel? user, required Map<String, dynamic> data}) async { {required UserModel? user, required Map<String, dynamic> data}) async {
try { try {
Response response = await _service.dio.put( Response response = await _service.dio.put(
'$baseUrl/$userUrl', '$baseUrl/$kUserUrl',
data: data, data: data,
); );
print(response.statusCode);
print(response.data);
if (response.statusCode == 200) { if (response.statusCode == 200) {
return { return {
'status': ResponseStatus.success, 'status': ResponseStatus.success,
@ -170,7 +194,6 @@ class ApiService {
}; };
} }
} catch (e) { } catch (e) {
print('Exception ${e.toString()}');
return { return {
'message': e.toString(), 'message': e.toString(),
'status': ResponseStatus.failure, 'status': ResponseStatus.failure,

View File

@ -5,6 +5,10 @@ import 'package:yimaru_app/services/secure_storage_service.dart';
class AuthenticationService { class AuthenticationService {
final _secureService = locator<SecureStorageService>(); final _secureService = locator<SecureStorageService>();
UserModel? _user;
UserModel? get user => _user;
Future<bool> userLoggedIn() async { Future<bool> userLoggedIn() async {
if (await _secureService.getString('userId') != null) { if (await _secureService.getString('userId') != null) {
return true; return true;
@ -28,14 +32,56 @@ class AuthenticationService {
await _secureService.setString('refreshToken', refresh); await _secureService.setString('refreshToken', refresh);
} }
Future<void> saveUserData(Map<String, dynamic> data) async { Future<void> saveUserName(Map<String, dynamic> 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<void> saveBasicUserData(Map<String, dynamic> data) async {
await _secureService.setInt('userId', data['userId']); await _secureService.setInt('userId', data['userId']);
await _secureService.setString('accessToken', data['accessToken']); await _secureService.setString('accessToken', data['accessToken']);
await _secureService.setString('refreshToken', data['refreshToken']); 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<void> saveProfileCompleted(bool value) async { Future<void> saveProfileStatus(bool value) async {
await _secureService.setBool('profileCompleted', value); 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<void> saveProfileImage() async {}
Future<void> saveFullName(Map<String, dynamic> 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<bool> isFirstTimeInstall() async => Future<bool> isFirstTimeInstall() async =>
@ -45,14 +91,16 @@ class AuthenticationService {
await _secureService.setBool('firstTimeInstall', value); await _secureService.setBool('firstTimeInstall', value);
} }
Future<UserModel> getUser() async { Future<UserModel?> getUser() async {
UserModel user = UserModel( _user = UserModel(
userId: await _secureService.getInt('userId'), userId: await _secureService.getInt('userId'),
firstName: await _secureService.getString('firstName'),
accessToken: await _secureService.getString('accessToken'), accessToken: await _secureService.getString('accessToken'),
refreshToken: await _secureService.getString('refreshToken'), refreshToken: await _secureService.getString('refreshToken'),
profileImage: await _secureService.getString('profileImage'),
profileCompleted: await _secureService.getBool('profileCompleted'), profileCompleted: await _secureService.getBool('profileCompleted'),
); );
return user; return _user;
} }
Future<void> logOut() async { Future<void> logOut() async {

View File

@ -125,18 +125,17 @@ class DioService {
} }
Future<bool> _refreshToken() async { Future<bool> _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 { try {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'role': 'STUDENT', 'role': 'STUDENT',
'user_id': user.userId, 'user_id': user?.userId,
'access_token': user.accessToken, 'access_token': user?.accessToken,
'refresh_token': user.refreshToken 'refresh_token': user?.refreshToken
}; };
print(data);
final response = await _refreshDio.post( final response = await _refreshDio.post(
'$baseUrl/$kRefreshTokenUrl', '$baseUrl/$kRefreshTokenUrl',
data: data, data: data,
@ -150,8 +149,6 @@ class DioService {
), ),
); );
print('Refresh response');
print(response.data);
await _authenticationService.saveTokens( await _authenticationService.saveTokens(
access: response.data['access_token'], access: response.data['access_token'],
refresh: response.data['refresh_token'], refresh: response.data['refresh_token'],
@ -159,10 +156,8 @@ class DioService {
return true; return true;
} catch (e) { } catch (e) {
print('Refresh response exception'); await _authenticationService.logOut();
print(e.toString()); await _navigationService.replaceWithLoginView();
// await _authenticationService.logOut();
// await _navigationService.replaceWithLoginView();
return false; return false;
} }
} }

View File

@ -0,0 +1 @@
class ImagePickerService {}

View File

@ -1,7 +1,9 @@
String baseUrl = 'http://195.35.29.82:8080'; String baseUrl = 'http://195.35.29.82:8080';
//String baseUrl = 'https://api.yimaru.yaltopia.com'; //String baseUrl = 'https://api.yimaru.yaltopia.com';
String userUrl = 'api/v1/user'; String kGetUserUrl = 'single';
String kUserUrl = 'api/v1/user';
String kRegisterUrl = 'register'; String kRegisterUrl = 'register';

View File

@ -171,15 +171,9 @@ PinTheme errorPinTheme = defaultPin.copyBorderWith(
border: Border.all(color: Colors.red), border: Border.all(color: Colors.red),
); );
TextStyle validationStyle = const TextStyle( TextStyle style18P600 = const TextStyle(
fontSize: 12, fontSize: 18,
color: Colors.red, color: kcPrimaryColor,
fontWeight: FontWeight.w700,
);
TextStyle style25DG600 = const TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
@ -189,6 +183,21 @@ TextStyle style12R700 = const TextStyle(
fontWeight: FontWeight.w700, 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( TextStyle style16DG600 = const TextStyle(
fontSize: 16, fontSize: 16,
color: kcDarkGrey, color: kcDarkGrey,
@ -210,13 +219,15 @@ TextStyle style14DG400 = const TextStyle(
color: kcDarkGrey, color: kcDarkGrey,
); );
TextStyle style14P400 = const TextStyle( TextStyle style14DG600 = const TextStyle(
color: kcPrimaryColor, color: kcDarkGrey,
fontWeight: FontWeight.w600,
); );
TextStyle style14P600 = const TextStyle( TextStyle validationStyle = const TextStyle(
color: kcPrimaryColor, fontSize: 12,
fontWeight: FontWeight.w600, color: Colors.red,
fontWeight: FontWeight.w700,
); );
Style htmlDefaultStyle = Style(color: kcDarkGrey, fontSize: FontSize(16)); Style htmlDefaultStyle = Style(color: kcDarkGrey, fontSize: FontSize(16));

View File

@ -5,16 +5,20 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/common/enmus.dart'; import 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.dialogs.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart'; import '../../../app/app.router.dart';
import '../../../models/assessment.dart'; import '../../../models/assessment.dart';
import '../../../models/user_model.dart'; import '../../../models/user_model.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
import '../home/home_view.dart'; import '../home/home_view.dart';
class AssessmentViewModel extends BaseViewModel { class AssessmentViewModel extends BaseViewModel {
final _apiService = locator<ApiService>(); final _apiService = locator<ApiService>();
final _dialogService = locator<DialogService>();
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
@ -73,9 +77,6 @@ class AssessmentViewModel extends BaseViewModel {
if (_currentQuestion == 5) { if (_currentQuestion == 5) {
// A1 // A1
final correctCount = countCorrectAnswersUntil(5); final correctCount = countCorrectAnswersUntil(5);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct A1: $correctCount');
if (correctCount > 3) { if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a1}; return {'continue': true, 'level': ProficiencyLevels.a1};
@ -86,9 +87,6 @@ class AssessmentViewModel extends BaseViewModel {
// A2 // A2
final correctCount = countCorrectAnswersUntil(10); final correctCount = countCorrectAnswersUntil(10);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct A2: $correctCount');
if (correctCount > 3) { if (correctCount > 3) {
return {'continue': true, 'level': ProficiencyLevels.a2}; return {'continue': true, 'level': ProficiencyLevels.a2};
@ -98,9 +96,6 @@ class AssessmentViewModel extends BaseViewModel {
} else if (_currentQuestion == 16) { } else if (_currentQuestion == 16) {
// B1 // B1
final correctCount = countCorrectAnswersUntil(16); final correctCount = countCorrectAnswersUntil(16);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct B1: $correctCount');
if (correctCount > 4) { if (correctCount > 4) {
return {'continue': true, 'level': ProficiencyLevels.b1}; return {'continue': true, 'level': ProficiencyLevels.b1};
@ -109,9 +104,6 @@ class AssessmentViewModel extends BaseViewModel {
} }
} else if (_currentQuestion == 22) { } else if (_currentQuestion == 22) {
final correctCount = countCorrectAnswersUntil(16); final correctCount = countCorrectAnswersUntil(16);
print('All : $_selectedAnswers');
print('Question page : $_currentQuestion');
print('Correct B2: $correctCount');
if (correctCount > 4) { if (correctCount > 4) {
return {'continue': true, 'level': ProficiencyLevels.b2}; return {'continue': true, 'level': ProficiencyLevels.b2};
@ -178,17 +170,26 @@ class AssessmentViewModel extends BaseViewModel {
} }
// Complete profile // Complete profile
Future<void> completeProfile() async {
Map<String, dynamic> response = Future<void> saveProfileCompleted() async {
await runBusyFuture<Map<String, dynamic>>(_completeProfile()); Map<String, dynamic> data = {'firstName': _userData['firstName']};
await _authenticationService.saveFullName(data);
} }
Future<void> completeProfile() async =>
await runBusyFuture<Map<String, dynamic>>(_completeProfile());
Future<Map<String, dynamic>> _completeProfile() async { Future<Map<String, dynamic>> _completeProfile() async {
print(_userData); UserModel? user = await _authenticationService.getUser();
UserModel user = await _authenticationService.getUser();
Map<String, dynamic> response = Map<String, dynamic> response =
await _apiService.updateProfile(data: _userData, user: user); 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; return response;
} }
@ -203,8 +204,7 @@ class AssessmentViewModel extends BaseViewModel {
} else { } else {
if (response['continue']) { if (response['continue']) {
_pageController.jumpToPage(_currentQuestion); _pageController.jumpToPage(_currentQuestion);
} } else {
{
_proficiencyLevel = response['level']; _proficiencyLevel = response['level'];
next(); next();
} }
@ -218,8 +218,6 @@ class AssessmentViewModel extends BaseViewModel {
_pageController.previousPage( _pageController.previousPage(
duration: const Duration(microseconds: 100), curve: Curves.linear); duration: const Duration(microseconds: 100), curve: Curves.linear);
rebuildUi(); rebuildUi();
} else {
_navigationService.back();
} }
} }
@ -238,12 +236,34 @@ class AssessmentViewModel extends BaseViewModel {
} }
void pop() { void pop() {
if (_currentPage != 0) { if (_currentPage == 3 /*7*/) {
_navigationService.back();
} else if (_currentPage != 0 && _currentPage != 3) {
_currentPage--; _currentPage--;
rebuildUi(); rebuildUi();
} }
} }
Future<bool?> 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<void> abort() async {
bool? response = await showAbortDialog();
if (response != null && response) {
next(page: 3);
}
}
Future<void> navigateToLanguage() async => Future<void> navigateToLanguage() async =>
await _navigationService.navigateToLanguageView(); await _navigationService.navigateToLanguageView();

View File

@ -45,9 +45,10 @@ class AssessmentFormScreen extends ViewModelWidget<AssessmentViewModel> {
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: true, onClose: viewModel.abort,
showLanguageSelection: false, showLanguageSelection: false,
onPop: viewModel.previousQuestion, onPop: viewModel.previousQuestion,
showBackButton: viewModel.currentQuestion == 0 ? false : true,
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>

View File

@ -58,7 +58,8 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
]; ];
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: true,
onPop: viewModel.pop,
showLanguageSelection: true, showLanguageSelection: true,
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
); );

View File

@ -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 'package:yimaru_app/ui/widgets/large_app_bar.dart';
import '../../../common/enmus.dart'; import '../../../common/enmus.dart';
import '../../../widgets/page_loading_indicator.dart';
import '../assessment_viewmodel.dart'; import '../assessment_viewmodel.dart';
class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> { class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
@ -15,13 +16,11 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Future<void> _start(AssessmentViewModel viewModel) async { Future<void> _start(AssessmentViewModel viewModel) async {
if (viewModel.proficiencyLevel != ProficiencyLevels.none) { if (viewModel.proficiencyLevel != ProficiencyLevels.none) {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'preferred_language': 'en',
'knowledge_level': viewModel.proficiencyLevel.name.toUpperCase() 'knowledge_level': viewModel.proficiencyLevel.name.toUpperCase()
}; };
viewModel.addUserData(data); viewModel.addUserData(data);
} }
await viewModel.completeProfile(); await viewModel.completeProfile();
} }
@ -31,20 +30,24 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffoldStack(viewModel),
); );
Widget _buildScaffoldStack(AssessmentViewModel viewModel) =>
Stack(children: [_buildScaffold(viewModel), _buildState(viewModel)]);
Widget _buildScaffold(AssessmentViewModel viewModel) => Column( Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) => List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
[_buildAppBar(), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar() => const LargeAppBar( Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
showBackButton: false, showBackButton: true,
showLanguageSelection: false, onPop: viewModel.pop,
showLanguageSelection: true,
); );
Widget _buildExpandedBody(AssessmentViewModel viewModel) => Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
@ -114,10 +117,13 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
Widget _buildContinueButton(AssessmentViewModel viewModel) => Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: 'Finish',
borderRadius: 12, borderRadius: 12,
text: 'Go to My Lessons',
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _start(viewModel), onTap: () async => await _start(viewModel),
); );
Widget _buildState(AssessmentViewModel viewModel) =>
viewModel.isBusy ? const PageLoadingIndicator() : Container();
} }

View File

@ -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<FailureViewModel> {
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<Widget> _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<Widget> _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<Widget> _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,
);
}

View File

@ -0,0 +1,3 @@
import 'package:stacked/stacked.dart';
class FailureViewModel extends BaseViewModel {}

View File

@ -15,8 +15,9 @@ class HomeView extends StackedView<HomeViewModel> {
HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel(); HomeViewModel viewModelBuilder(BuildContext context) => HomeViewModel();
@override @override
void onViewModelReady(HomeViewModel viewModel) { void onViewModelReady(HomeViewModel viewModel) async {
viewModel.getProfileStatus(); await viewModel.getProfileStatus();
await viewModel.getProfileData();
super.onViewModelReady(viewModel); super.onViewModelReady(viewModel);
} }

View File

@ -7,6 +7,10 @@ import 'package:yimaru_app/services/status_checker_service.dart';
import 'package:yimaru_app/ui/common/app_strings.dart'; import 'package:yimaru_app/ui/common/app_strings.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package: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/api_service.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
@ -46,28 +50,69 @@ class HomeViewModel extends BaseViewModel {
); );
} }
Future<void> saveFullName(String name) async {
Map<String, dynamic> data = {
'firstName': name,
};
await _authenticationService.saveFullName(data);
}
Future<void> saveProfileStatus(bool value) async =>
await _authenticationService.saveProfileStatus(value);
// Navigation // Navigation
Future<void> replaceWithFailure() async =>
await _navigationService.clearStackAndShowView(
const FailureView(label: 'Check your internet connection to proceed'),
);
Future<void> replaceWithOnboarding() async => Future<void> replaceWithOnboarding() async =>
await _navigationService.replaceWithOnboardingView(); await _navigationService.replaceWithOnboardingView();
// Remote api calls // Remote api calls
Future<void> getProfileData() async =>
await runBusyFuture<Map<String, dynamic>>(_getProfileData());
Future<Map<String, dynamic>> _getProfileData() async {
Map<String, dynamic> 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<String, dynamic> data = {
'firstName': response['data']['first_name']
};
await _authenticationService.saveFullName(data);
}
}
}
return response;
}
Future<void> getProfileStatus() async { Future<void> getProfileStatus() async {
Map<String, dynamic> response = Map<String, dynamic> response =
await runBusyFuture<Map<String, dynamic>>(_getProfileStatus()); await runBusyFuture<Map<String, dynamic>>(_getProfileStatus());
if (response['status'] == ResponseStatus.success && !response['data']) { if (response['status'] == ResponseStatus.success && !response['data']) {
await replaceWithOnboarding(); await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
await saveProfileStatus(response['data']);
} }
} }
Future<Map<String, dynamic>> _getProfileStatus() async { Future<Map<String, dynamic>> _getProfileStatus() async {
Map<String, dynamic> response = {}; Map<String, dynamic> response = {};
UserModel user = await _authenticationService.getUser();
if (user.profileCompleted == null) { UserModel? user = await _authenticationService.getUser();
if (user?.profileCompleted == null) {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileStatus(user); response = await _apiService.getProfileStatus(user);
} else { } else {
response = {'data': false, 'status': ResponseStatus.success}; await replaceWithFailure();
} }
} else { } else {
response = {'data': true, 'status': ResponseStatus.success}; response = {'data': true, 'status': ResponseStatus.success};

View File

@ -38,12 +38,15 @@ class LearnView extends StackedView<LearnViewModel> {
Widget _buildColumn(LearnViewModel viewModel) => Column( Widget _buildColumn(LearnViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(), _buildAppBar(viewModel),
_buildLevelsColumnWrapper(viewModel) _buildLevelsColumnWrapper(viewModel)
], ],
); );
Widget _buildAppBar() => const LearnAppBar(); Widget _buildAppBar(LearnViewModel viewModel) => LearnAppBar(
name: viewModel.user?.firstName,
profileImage: viewModel.user?.profileImage,
);
Widget _buildLevelsColumnWrapper(LearnViewModel viewModel) => Widget _buildLevelsColumnWrapper(LearnViewModel viewModel) =>
Expanded(child: _buildLevelsColumnScrollView(viewModel)); Expanded(child: _buildLevelsColumnScrollView(viewModel));

View File

@ -1,12 +1,19 @@
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.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 'package:yimaru_app/ui/common/enmus.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
class LearnViewModel extends BaseViewModel { class LearnViewModel extends BaseViewModel {
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
late final UserModel? _user = _authenticationService.user;
UserModel? get user => _user;
final List<Map<String, dynamic>> _learnLevels = [ final List<Map<String, dynamic>> _learnLevels = [
{ {

View File

@ -37,5 +37,6 @@ class LearnModuleViewModel extends BaseViewModel {
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnLesson() async=> await _navigationService.navigateToLearnLessonView(); Future<void> navigateToLearnLesson() async =>
await _navigationService.navigateToLearnLessonView();
} }

View File

@ -126,7 +126,7 @@ class LoginViewModel extends FormViewModel {
'refreshToken': user.refreshToken 'refreshToken': user.refreshToken
}; };
await _authenticationService.saveUserData(data); await _authenticationService.saveBasicUserData(data);
showSuccessToast(response['message']); showSuccessToast(response['message']);
} else { } else {
showErrorToast(response['message']); showErrorToast(response['message']);

View File

@ -57,7 +57,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) => Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
PopScope( PopScope(
canPop: false, canPop: viewModel.currentPage == 0 ? true : false,
onPopInvokedWithResult: (value, data) => viewModel.pop(), onPopInvokedWithResult: (value, data) => viewModel.pop(),
child: _buildOnboardingScreens(viewModel)); child: _buildOnboardingScreens(viewModel));

View File

@ -54,10 +54,13 @@ class OnboardingViewModel extends FormViewModel {
// Age group // Age group
final List<String> _ageGroups = [ final List<String> _ageGroups = [
'8-14', 'UNDER_13',
'15-18', '13_17',
'19-26', '18_24',
'26+', '25_34',
'35_44',
'45_54',
'55_PLUS'
]; ];
List<String> get ageGroups => _ageGroups; List<String> get ageGroups => _ageGroups;
@ -352,7 +355,6 @@ class OnboardingViewModel extends FormViewModel {
// Add user data // Add user data
void addUserData(Map<String, dynamic> data) { void addUserData(Map<String, dynamic> data) {
_userData.addAll(data); _userData.addAll(data);
print('User data : $_userData');
} }
void clearUserData() { void clearUserData() {
@ -385,7 +387,7 @@ class OnboardingViewModel extends FormViewModel {
} }
void pop() { void pop() {
if (_currentPage == 8) { if (_currentPage == 0) {
_navigationService.back(); _navigationService.back();
} else { } else {
_currentPage--; _currentPage--;

View File

@ -15,11 +15,7 @@ class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
Future<void> _next(OnboardingViewModel viewModel) async { Future<void> _next(OnboardingViewModel viewModel) async {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {'birth_day': viewModel.selectedBirthday};
'birth_day': DateFormat('yyyy-MM-dd')
.parseUTC(viewModel.selectedBirthday ?? DateTime.now().toString())
.toIso8601String()
};
viewModel.addUserData(data); viewModel.addUserData(data);
viewModel.next(); viewModel.next();
} }

View File

@ -93,13 +93,16 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
onLanguage: () async => await viewModel.navigateToLanguage(), onLanguage: () async => await viewModel.navigateToLanguage(),
); );
Widget _buildTitle(OnboardingViewModel viewModel) => Text( Widget _buildTitle(OnboardingViewModel viewModel) => Text.rich(
'Hi ${viewModel.userData['first_name']}, Choose your learning goal.', TextSpan(
style: const TextStyle( text: 'Hi ${viewModel.userData['first_name']},',
fontSize: 25, style: style18P600.copyWith(fontSize: 22),
color: kcDarkGrey, children: [
fontWeight: FontWeight.w600, TextSpan(
), text: ' Choose your learning goal.',
style: style16DG600.copyWith(fontSize: 22),
)
]),
); );
Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder( Widget _buildLearningGoals(OnboardingViewModel viewModel) => ListView.builder(

View File

@ -17,6 +17,8 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
FocusManager.instance.primaryFocus?.unfocus(); FocusManager.instance.primaryFocus?.unfocus();
Map<String, dynamic> data = { Map<String, dynamic> data = {
'profile_completed': true,
'preferred_language': 'en',
'favoutite_topic': viewModel.selectedTopic ?? topicController.text, 'favoutite_topic': viewModel.selectedTopic ?? topicController.text,
}; };
viewModel.addUserData(data); viewModel.addUserData(data);

View File

@ -47,7 +47,7 @@ class ProfileView extends StackedView<ProfileViewModel> {
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildNotificationIconWrapper(), _buildNotificationIconWrapper(),
_buildProfileSection(), _buildProfileSection(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildViewProfileButton(viewModel), _buildViewProfileButton(viewModel),
verticalSpaceLarge, verticalSpaceLarge,
@ -66,27 +66,25 @@ class ProfileView extends StackedView<ProfileViewModel> {
color: kcDarkGrey, color: kcDarkGrey,
); );
Widget _buildProfileSection() => Column( Widget _buildProfileSection(ProfileViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: _buildProfileSectionChildren(), children: _buildProfileSectionChildren(viewModel),
); );
List<Widget> _buildProfileSectionChildren() => [ List<Widget> _buildProfileSectionChildren(ProfileViewModel viewModel) => [
_buildProfileImage(), _buildProfileImage(viewModel),
verticalSpaceSmall, verticalSpaceSmall,
_buildProfileName(), _buildProfileName(viewModel),
]; ];
Widget _buildProfileImage() => const ProfileImage(); Widget _buildProfileImage(ProfileViewModel viewModel) => ProfileImage(
profileImage: viewModel.user?.profileImage,
);
Widget _buildProfileName() => const Text( Widget _buildProfileName(ProfileViewModel viewModel) => Text(
'Hi, Bisrat 👋', 'Hi, ${viewModel.user?.firstName ?? ''} 👋',
style: TextStyle( style: style25DG600,
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
); );
Widget _buildViewProfileButton(ProfileViewModel viewModel) => Widget _buildViewProfileButton(ProfileViewModel viewModel) =>

View File

@ -3,6 +3,7 @@ import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/app/app.router.dart'; import 'package:yimaru_app/app/app.router.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/user_model.dart';
import '../../../services/authentication_service.dart'; import '../../../services/authentication_service.dart';
class ProfileViewModel extends BaseViewModel { class ProfileViewModel extends BaseViewModel {
@ -10,6 +11,10 @@ class ProfileViewModel extends BaseViewModel {
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
late final UserModel? _user = _authenticationService.user;
UserModel? get user => _user;
Future<void> logOut() async { Future<void> logOut() async {
await _authenticationService.logOut(); await _authenticationService.logOut();
await _navigationService.replaceWithLoginView(); await _navigationService.replaceWithLoginView();

View File

@ -101,7 +101,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
List<Widget> _buildColumnChildren(ProfileDetailViewModel viewModel) => [ List<Widget> _buildColumnChildren(ProfileDetailViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,
_buildProfileImage(), _buildProfileImageWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildNameFormSection(viewModel), _buildNameFormSection(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
@ -120,9 +120,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildLowerColumn(viewModel) _buildLowerColumn(viewModel)
]; ];
Widget _buildProfileImage() => Widget _buildProfileImageWrapper(ProfileDetailViewModel viewModel) =>
const Align(alignment: Alignment.center, child: ProfileImage()); Align(alignment: Alignment.center, child: _buildProfileImage(viewModel));
Widget _buildProfileImage(ProfileDetailViewModel viewModel) => ProfileImage(
profileImage: viewModel.user?.profileImage,
);
Widget _buildNameFormSection(ProfileDetailViewModel viewModel) => Row( Widget _buildNameFormSection(ProfileDetailViewModel viewModel) => Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildNameFormChildren(viewModel), children: _buildNameFormChildren(viewModel),

View File

@ -2,9 +2,18 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../models/user_model.dart';
import '../../../services/authentication_service.dart';
class ProfileDetailViewModel extends FormViewModel { class ProfileDetailViewModel extends FormViewModel {
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _authenticationService = locator<AuthenticationService>();
late final UserModel? _user = _authenticationService.user;
UserModel? get user => _user;
// First name // First name
bool _focusFirstName = false; bool _focusFirstName = false;

View File

@ -246,7 +246,7 @@ class RegisterViewModel extends FormViewModel {
// 'refreshToken': 'refreshToken' // 'refreshToken': 'refreshToken'
// } // }
await _authenticationService.saveUserData(data); await _authenticationService.saveBasicUserData(data);
showSuccessToast(response['message']); showSuccessToast(response['message']);
} else { } else {
showErrorToast(response['message']); showErrorToast(response['message']);

View File

@ -6,11 +6,13 @@ class LargeAppBar extends StatelessWidget {
final bool showBackButton; final bool showBackButton;
final GestureTapCallback? onPop; final GestureTapCallback? onPop;
final bool showLanguageSelection; final bool showLanguageSelection;
final GestureTapCallback? onClose;
final GestureTapCallback? onLanguage; final GestureTapCallback? onLanguage;
const LargeAppBar( const LargeAppBar(
{super.key, {super.key,
this.onPop, this.onPop,
this.onClose,
this.onLanguage, this.onLanguage,
required this.showBackButton, required this.showBackButton,
required this.showLanguageSelection}); required this.showLanguageSelection});
@ -53,9 +55,9 @@ class LargeAppBar extends StatelessWidget {
Widget _buildRightButton() => Align( Widget _buildRightButton() => Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
child: showLanguageSelection ? _buildLanguageSelector() : Container() child: showLanguageSelection
// _buildCloseButton() ? _buildLanguageSelector()
); : _buildCloseButton());
Widget _buildLanguageSelector() => LanguageButton( Widget _buildLanguageSelector() => LanguageButton(
language: 'EN', language: 'EN',
@ -63,8 +65,9 @@ class LargeAppBar extends StatelessWidget {
); );
Widget _buildCloseButton() => IconButton( Widget _buildCloseButton() => IconButton(
onPressed: () {}, onPressed: onClose,
icon: _buildCloseIcon(), icon: _buildCloseIcon(),
padding: const EdgeInsets.only(top: 5),
); );
Widget _buildCloseIcon() => const Icon( Widget _buildCloseIcon() => const Icon(

View File

@ -1,10 +1,15 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart'; import 'package:yimaru_app/ui/common/ui_helpers.dart';
import '../common/app_colors.dart'; import '../common/app_colors.dart';
class LearnAppBar extends StatelessWidget { 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 @override
Widget build(BuildContext context) => _buildStack(); Widget build(BuildContext context) => _buildStack();
@ -30,9 +35,22 @@ class LearnAppBar extends StatelessWidget {
List<Widget> _buildProfileRowChildren() => List<Widget> _buildProfileRowChildren() =>
[_buildProfileImage(), horizontalSpaceSmall, _buildGreetingTextColumn()]; [_buildProfileImage(), horizontalSpaceSmall, _buildGreetingTextColumn()];
Widget _buildProfileImage() => const CircleAvatar( Widget _buildProfileImage() => CircleAvatar(
radius: 25, 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( Widget _buildGreetingTextColumn() => Column(
@ -44,28 +62,19 @@ class LearnAppBar extends StatelessWidget {
List<Widget> _buildGreetingChildren() => List<Widget> _buildGreetingChildren() =>
[_buildGreetingTitle(), _buildSubTitle()]; [_buildGreetingTitle(), _buildSubTitle()];
Widget _buildGreetingTitle() => const Text.rich( Widget _buildGreetingTitle() => Text.rich(
TextSpan( TextSpan(text: 'Hello,', style: style14DG600, children: [
text: 'Hello,', TextSpan(
style: TextStyle( text: ' $name!',
color: kcDarkGrey, style: style14P600,
fontWeight: FontWeight.w600, )
), ]),
children: [
TextSpan(
text: ' Bisrat!',
style: TextStyle(
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
),
)
]),
); );
Widget _buildSubTitle() => const Text( Widget _buildSubTitle() => Text(
'Ready to keep learning English today?', 'Ready to keep learning English today?',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(color: kcMediumGrey), style: style14DG400,
); );
Widget _buildNotificationIconWrapper() => Widget _buildNotificationIconWrapper() =>

View File

@ -192,7 +192,7 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
); );
Widget _buildLessonButton(LearnModuleViewModel viewModel) => Widget _buildLessonButton(LearnModuleViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 15, height: 15,
borderRadius: 12, borderRadius: 12,
text: 'View Lessons', text: 'View Lessons',

View File

@ -1,8 +1,10 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
class ProfileImage extends StatelessWidget { class ProfileImage extends StatelessWidget {
const ProfileImage({super.key}); final String? profileImage;
const ProfileImage({super.key, required this.profileImage});
@override @override
Widget build(BuildContext context) => _buildSizedBox(); Widget build(BuildContext context) => _buildSizedBox();
@ -22,9 +24,22 @@ class ProfileImage extends StatelessWidget {
child: _buildProfileImage(), child: _buildProfileImage(),
); );
Widget _buildProfileImage() => const CircleAvatar( Widget _buildProfileImage() => CircleAvatar(
radius: 50, 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( Widget _buildCameraButtonWrapper() => Align(

View File

@ -8,9 +8,11 @@ import Foundation
import battery_plus import battery_plus
import connectivity_plus import connectivity_plus
import flutter_secure_storage_darwin import flutter_secure_storage_darwin
import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin")) BatteryPlusMacosPlugin.register(with: registry.registrar(forPlugin: "BatteryPlusMacosPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
} }

View File

@ -129,6 +129,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.12.3" 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: characters:
dependency: transitive dependency: transitive
description: description:
@ -318,6 +342,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.1" 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: flutter_html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -704,6 +736,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.2.3" 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: omni_datetime_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@ -917,6 +957,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.1" 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: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -989,6 +1069,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
version: "3.4.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:

View File

@ -27,8 +27,10 @@ dependencies:
stacked_services: ^1.1.0 stacked_services: ^1.1.0
omni_datetime_picker: any omni_datetime_picker: any
json_serializable: ^6.8.0 json_serializable: ^6.8.0
cached_network_image: ^3.4.1
flutter_secure_storage: ^10.0.0 flutter_secure_storage: ^10.0.0
flutter_timer_countdown: ^1.0.7 flutter_timer_countdown: ^1.0.7
internet_connection_checker_plus: ^2.9.1+2 internet_connection_checker_plus: ^2.9.1+2
dev_dependencies: dev_dependencies:

View File

@ -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<NavigationService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<BottomSheetService>(onMissingStub: OnMissingStub.returnDefault),
MockSpec<DialogService>(onMissingStub: OnMissingStub.returnDefault),
// @stacked-mock-spec
],
)
void registerServices() {
getAndRegisterNavigationService();
getAndRegisterBottomSheetService();
getAndRegisterDialogService();
// @stacked-mock-register
}
MockNavigationService getAndRegisterNavigationService() {
_removeRegistrationIfExists<NavigationService>();
final service = MockNavigationService();
locator.registerSingleton<NavigationService>(service);
return service;
}
MockBottomSheetService getAndRegisterBottomSheetService<T>({
SheetResponse<T>? showCustomSheetResponse,
}) {
_removeRegistrationIfExists<BottomSheetService>();
final service = MockBottomSheetService();
when(
service.showCustomSheet<T, T>(
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<T>()),
);
locator.registerSingleton<BottomSheetService>(service);
return service;
}
MockDialogService getAndRegisterDialogService() {
_removeRegistrationIfExists<DialogService>();
final service = MockDialogService();
locator.registerSingleton<DialogService>(service);
return service;
}
// @stacked-mock-create
void _removeRegistrationIfExists<T extends Object>() {
if (locator.isRegistered<T>()) {
locator.unregister<T>();
}
}

View File

@ -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<String>(
this,
Invocation.getter(#previousRoute),
),
returnValueForMissingStub: _i3.dummyValue<String>(
this,
Invocation.getter(#previousRoute),
),
) as String);
@override
String get currentRoute => (super.noSuchMethod(
Invocation.getter(#currentRoute),
returnValue: _i3.dummyValue<String>(
this,
Invocation.getter(#currentRoute),
),
returnValueForMissingStub: _i3.dummyValue<String>(
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<T?>? navigateWithTransition<T>(
_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<T?>?);
@override
_i5.Future<T?>? replaceWithTransition<T>(
_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<T?>?);
@override
bool back<T>({
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<T?>? navigateTo<T>(
String? routeName, {
dynamic arguments,
int? id,
bool? preventDuplicates = true,
Map<String, String>? 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<T?>?);
@override
_i5.Future<T?>? navigateToView<T>(
_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<T?>?);
@override
_i5.Future<T?>? replaceWith<T>(
String? routeName, {
dynamic arguments,
int? id,
bool? preventDuplicates = true,
Map<String, String>? 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<T?>?);
@override
_i5.Future<T?>? clearStackAndShow<T>(
String? routeName, {
dynamic arguments,
int? id,
Map<String, String>? parameters,
}) =>
(super.noSuchMethod(
Invocation.method(
#clearStackAndShow,
[routeName],
{
#arguments: arguments,
#id: id,
#parameters: parameters,
},
),
returnValueForMissingStub: null,
) as _i5.Future<T?>?);
@override
_i5.Future<T?>? clearStackAndShowView<T>(
_i4.Widget? view, {
dynamic arguments,
int? id,
}) =>
(super.noSuchMethod(
Invocation.method(
#clearStackAndShowView,
[view],
{
#arguments: arguments,
#id: id,
},
),
returnValueForMissingStub: null,
) as _i5.Future<T?>?);
@override
_i5.Future<T?>? clearTillFirstAndShow<T>(
String? routeName, {
dynamic arguments,
int? id,
bool? preventDuplicates = true,
Map<String, String>? parameters,
}) =>
(super.noSuchMethod(
Invocation.method(
#clearTillFirstAndShow,
[routeName],
{
#arguments: arguments,
#id: id,
#preventDuplicates: preventDuplicates,
#parameters: parameters,
},
),
returnValueForMissingStub: null,
) as _i5.Future<T?>?);
@override
_i5.Future<T?>? clearTillFirstAndShowView<T>(
_i4.Widget? view, {
dynamic arguments,
int? id,
}) =>
(super.noSuchMethod(
Invocation.method(
#clearTillFirstAndShowView,
[view],
{
#arguments: arguments,
#id: id,
},
),
returnValueForMissingStub: null,
) as _i5.Future<T?>?);
@override
_i5.Future<T?>? pushNamedAndRemoveUntil<T>(
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<T?>?);
}
/// 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<dynamic, _i2.SheetBuilder>? builders) =>
super.noSuchMethod(
Invocation.method(
#setCustomSheetBuilders,
[builders],
),
returnValueForMissingStub: null,
);
@override
_i5.Future<_i2.SheetResponse<dynamic>?> 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<dynamic>?>.value(),
returnValueForMissingStub:
_i5.Future<_i2.SheetResponse<dynamic>?>.value(),
) as _i5.Future<_i2.SheetResponse<dynamic>?>);
@override
_i5.Future<_i2.SheetResponse<T>?> showCustomSheet<T, R>({
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<T>?>.value(),
returnValueForMissingStub: _i5.Future<_i2.SheetResponse<T>?>.value(),
) as _i5.Future<_i2.SheetResponse<T>?>);
@override
void completeSheet(_i2.SheetResponse<dynamic>? 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<dynamic, _i2.DialogBuilder>? builders) =>
super.noSuchMethod(
Invocation.method(
#registerCustomDialogBuilders,
[builders],
),
returnValueForMissingStub: null,
);
@override
void registerCustomDialogBuilder({
required dynamic variant,
required _i4.Widget Function(
_i4.BuildContext,
_i2.DialogRequest<dynamic>,
dynamic Function(_i2.DialogResponse<dynamic>),
)? builder,
}) =>
super.noSuchMethod(
Invocation.method(
#registerCustomDialogBuilder,
[],
{
#variant: variant,
#builder: builder,
},
),
returnValueForMissingStub: null,
);
@override
_i5.Future<_i2.DialogResponse<dynamic>?> 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<dynamic>?>.value(),
returnValueForMissingStub:
_i5.Future<_i2.DialogResponse<dynamic>?>.value(),
) as _i5.Future<_i2.DialogResponse<dynamic>?>);
@override
_i5.Future<_i2.DialogResponse<T>?> showCustomDialog<T, R>({
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<T>?>.value(),
returnValueForMissingStub: _i5.Future<_i2.DialogResponse<T>?>.value(),
) as _i5.Future<_i2.DialogResponse<T>?>);
@override
_i5.Future<_i2.DialogResponse<dynamic>?> 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<dynamic>?>.value(),
returnValueForMissingStub:
_i5.Future<_i2.DialogResponse<dynamic>?>.value(),
) as _i5.Future<_i2.DialogResponse<dynamic>?>);
@override
void completeDialog(_i2.DialogResponse<dynamic>? response) =>
super.noSuchMethod(
Invocation.method(
#completeDialog,
[response],
),
returnValueForMissingStub: null,
);
}

View File

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

View File

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