feat(auth): Add forget password feature
This commit is contained in:
parent
8110e25cb9
commit
94c0576a87
|
|
@ -35,6 +35,7 @@ import 'package:yimaru_app/services/permission_handler_service.dart';
|
|||
import 'package:yimaru_app/services/image_picker_service.dart';
|
||||
import 'package:yimaru_app/services/google_auth_service.dart';
|
||||
import 'package:yimaru_app/services/image_downloader_service.dart';
|
||||
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart';
|
||||
// @stacked-import
|
||||
|
||||
@StackedApp(
|
||||
|
|
@ -63,6 +64,7 @@ import 'package:yimaru_app/services/image_downloader_service.dart';
|
|||
MaterialRoute(page: AssessmentView),
|
||||
MaterialRoute(page: LearnLessonView),
|
||||
MaterialRoute(page: FailureView),
|
||||
MaterialRoute(page: ForgetPasswordView),
|
||||
// @stacked-route
|
||||
],
|
||||
dependencies: [
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
||||
import 'package:flutter/material.dart' as _i26;
|
||||
import 'package:flutter/material.dart' as _i27;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart' as _i1;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i27;
|
||||
import 'package:stacked_services/stacked_services.dart' as _i28;
|
||||
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;
|
||||
|
|
@ -16,6 +16,8 @@ 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/forget_password/forget_password_view.dart'
|
||||
as _i26;
|
||||
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;
|
||||
|
|
@ -92,6 +94,8 @@ class Routes {
|
|||
|
||||
static const failureView = '/failure-view';
|
||||
|
||||
static const forgetPasswordView = '/forget-password-view';
|
||||
|
||||
static const all = <String>{
|
||||
homeView,
|
||||
onboardingView,
|
||||
|
|
@ -117,6 +121,7 @@ class Routes {
|
|||
assessmentView,
|
||||
learnLessonView,
|
||||
failureView,
|
||||
forgetPasswordView,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -218,17 +223,21 @@ class StackedRouter extends _i1.RouterBase {
|
|||
Routes.failureView,
|
||||
page: _i25.FailureView,
|
||||
),
|
||||
_i1.RouteDef(
|
||||
Routes.forgetPasswordView,
|
||||
page: _i26.ForgetPasswordView,
|
||||
),
|
||||
];
|
||||
|
||||
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
||||
_i2.HomeView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i2.HomeView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i3.OnboardingView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i3.OnboardingView(),
|
||||
settings: data,
|
||||
);
|
||||
|
|
@ -237,141 +246,147 @@ class StackedRouter extends _i1.RouterBase {
|
|||
final args = data.getArgs<StartupViewArguments>(
|
||||
orElse: () => const StartupViewArguments(),
|
||||
);
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i5.ProfileView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i5.ProfileView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i6.ProfileDetailView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i6.ProfileDetailView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i7.DownloadsView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i7.DownloadsView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i8.ProgressView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i8.ProgressView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i9.OngoingProgressView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i9.OngoingProgressView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i10.AccountPrivacyView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i10.AccountPrivacyView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i11.SupportView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i11.SupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i12.TelegramSupportView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i12.TelegramSupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i13.CallSupportView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i13.CallSupportView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i14.LanguageView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i14.LanguageView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i15.PrivacyPolicyView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i15.PrivacyPolicyView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i16.TermsAndConditionsView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i16.TermsAndConditionsView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i17.RegisterView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i17.RegisterView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i18.LoginView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i18.LoginView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i19.LearnView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i19.LearnView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i20.LearnLevelView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i20.LearnLevelView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i21.LearnModuleView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i21.LearnModuleView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i22.WelcomeView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i22.WelcomeView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i23.AssessmentView: (data) {
|
||||
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) =>
|
||||
_i23.AssessmentView(key: args.key, data: args.data),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i24.LearnLessonView: (data) {
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i24.LearnLessonView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i25.FailureView: (data) {
|
||||
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
||||
return _i26.MaterialPageRoute<dynamic>(
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) =>
|
||||
_i25.FailureView(key: args.key, label: args.label),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
_i26.ForgetPasswordView: (data) {
|
||||
return _i27.MaterialPageRoute<dynamic>(
|
||||
builder: (context) => const _i26.ForgetPasswordView(),
|
||||
settings: data,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@override
|
||||
|
|
@ -387,7 +402,7 @@ class StartupViewArguments {
|
|||
this.label = 'Loading',
|
||||
});
|
||||
|
||||
final _i26.Key? key;
|
||||
final _i27.Key? key;
|
||||
|
||||
final String label;
|
||||
|
||||
|
|
@ -414,7 +429,7 @@ class AssessmentViewArguments {
|
|||
required this.data,
|
||||
});
|
||||
|
||||
final _i26.Key? key;
|
||||
final _i27.Key? key;
|
||||
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
|
|
@ -441,7 +456,7 @@ class FailureViewArguments {
|
|||
required this.label,
|
||||
});
|
||||
|
||||
final _i26.Key? key;
|
||||
final _i27.Key? key;
|
||||
|
||||
final String label;
|
||||
|
||||
|
|
@ -462,7 +477,7 @@ class FailureViewArguments {
|
|||
}
|
||||
}
|
||||
|
||||
extension NavigatorStateExtension on _i27.NavigationService {
|
||||
extension NavigatorStateExtension on _i28.NavigationService {
|
||||
Future<dynamic> navigateToHomeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -492,7 +507,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToStartupView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
String label = 'Loading',
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -761,7 +776,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToAssessmentView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
required Map<String, dynamic> data,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -792,7 +807,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> navigateToFailureView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
required String label,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -808,6 +823,20 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateToForgetPasswordView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return navigateTo<dynamic>(Routes.forgetPasswordView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithHomeView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -837,7 +866,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithStartupView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
String label = 'Loading',
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1106,7 +1135,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithAssessmentView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
required Map<String, dynamic> data,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1137,7 +1166,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
}
|
||||
|
||||
Future<dynamic> replaceWithFailureView({
|
||||
_i26.Key? key,
|
||||
_i27.Key? key,
|
||||
required String label,
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
|
|
@ -1152,4 +1181,18 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
|||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
|
||||
Future<dynamic> replaceWithForgetPasswordView([
|
||||
int? routerId,
|
||||
bool preventDuplicates = true,
|
||||
Map<String, String>? parameters,
|
||||
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
|
||||
transition,
|
||||
]) async {
|
||||
return replaceWith<dynamic>(Routes.forgetPasswordView,
|
||||
id: routerId,
|
||||
preventDuplicates: preventDuplicates,
|
||||
parameters: parameters,
|
||||
transition: transition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,8 +38,6 @@ class UserModel {
|
|||
@JsonKey(name: 'profile_picture_url')
|
||||
final String? profilePicture;
|
||||
|
||||
|
||||
|
||||
const UserModel({
|
||||
this.email,
|
||||
this.region,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
|||
);
|
||||
|
||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
||||
'email': instance.email,
|
||||
'gender': instance.gender,
|
||||
'region': instance.region,
|
||||
'country': instance.country,
|
||||
'occupation': instance.occupation,
|
||||
'user_id': instance.userId,
|
||||
'last_name': instance.lastName,
|
||||
'birth_day': instance.birthday,
|
||||
|
|
@ -31,9 +36,4 @@ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
|||
'refresh_token': instance.refreshToken,
|
||||
'profile_completed': instance.profileCompleted,
|
||||
'profile_picture_url': instance.profilePicture,
|
||||
'email': instance.email,
|
||||
'gender': instance.gender,
|
||||
'region': instance.region,
|
||||
'country': instance.country,
|
||||
'occupation': instance.occupation,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ class ApiService {
|
|||
'message': 'Unknown Error Occurred'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Login
|
||||
// Email Login
|
||||
Future<Map<String, dynamic>> emailLogin(Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _service.dio.post(
|
||||
|
|
@ -57,10 +57,10 @@ class ApiService {
|
|||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -85,10 +85,10 @@ class ApiService {
|
|||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -112,10 +112,10 @@ class ApiService {
|
|||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -139,10 +139,65 @@ class ApiService {
|
|||
'message': 'Unknown Error Occurred'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Request reset code
|
||||
Future<Map<String, dynamic>> requestResetCode(
|
||||
Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _service.dio.post(
|
||||
'$kBaseUrl/$kUserUrl/$kRequestResetCode',
|
||||
data: data,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return {
|
||||
'status': ResponseStatus.success,
|
||||
'message': 'Reset code sent successfully',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': ResponseStatus.failure,
|
||||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Reset password
|
||||
Future<Map<String, dynamic>> resetPassword(Map<String, dynamic> data) async {
|
||||
try {
|
||||
Response response = await _service.dio.post(
|
||||
'$kBaseUrl/$kUserUrl/$kResetPassword',
|
||||
data: data,
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
return {
|
||||
'status': ResponseStatus.success,
|
||||
'message': 'Password reset successfully',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': ResponseStatus.failure,
|
||||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -166,10 +221,10 @@ class ApiService {
|
|||
'message': '${response.data['message']}, ${response.data['error']}'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -193,10 +248,10 @@ class ApiService {
|
|||
'message': 'Unknown Error Occurred'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -221,10 +276,10 @@ class ApiService {
|
|||
'message': 'Unknown Error Occurred'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -273,105 +328,14 @@ class ApiService {
|
|||
'message': 'Unknown Error Occurred'
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
} on DioException catch (e) {
|
||||
return {
|
||||
'message': e.toString(),
|
||||
'status': ResponseStatus.failure,
|
||||
'message': e.response?.data.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// // Update profile
|
||||
// Future<Map<String, dynamic>> updateProfile(
|
||||
// Map<String, dynamic> data) async {
|
||||
// try {
|
||||
// late FormData formData;
|
||||
//
|
||||
// if (data['profile_picture_url']
|
||||
// .toString()
|
||||
// .contains('com.ke.wede.customer.app/')) {
|
||||
// formData = FormData.fromMap({
|
||||
// 'gender': data['gender'],
|
||||
// 'region': data['region'],
|
||||
// 'country': data['country'],
|
||||
// 'last_name': data['last_name'],
|
||||
// 'nick_name': data['nick_name'],
|
||||
// 'birth_day': data['birth_day'],
|
||||
// 'age_group': data['age_group'],
|
||||
// 'occupation': data['occupation'],
|
||||
// 'first_name': data['first_name'],
|
||||
// 'learning_goal': data['learning_goal'],
|
||||
// 'language_goal': data['language_goal'],
|
||||
// 'education_level': data['education_level'],
|
||||
// 'favoutite_topic': data['favoutite_topic'],
|
||||
// 'knowledge_level': data['knowledge_level'],
|
||||
// 'profile_completed': data['profile_completed'],
|
||||
// 'preferred_language': data['preferred_language'],
|
||||
// 'language_challange': data['language_challange'],
|
||||
// 'profile_picture_url': data['profile_picture_url']
|
||||
// .toString()
|
||||
// .isNotEmpty
|
||||
// ? MultipartFile.fromFileSync(
|
||||
// data['profile_picture_url'],
|
||||
// filename:
|
||||
// data['profile_picture_url'].toString().split('/').last,
|
||||
// )
|
||||
// : null,
|
||||
// });
|
||||
// } else {
|
||||
// formData = FormData.fromMap({
|
||||
// 'gender': data['gender'],
|
||||
// 'region': data['region'],
|
||||
// 'country': data['country'],
|
||||
// 'last_name': data['last_name'],
|
||||
// 'nick_name': data['nick_name'],
|
||||
// 'birth_day': data['birth_day'],
|
||||
// 'age_group': data['age_group'],
|
||||
// 'occupation': data['occupation'],
|
||||
// 'first_name': data['first_name'],
|
||||
// 'learning_goal': data['learning_goal'],
|
||||
// 'language_goal': data['language_goal'],
|
||||
// 'education_level': data['education_level'],
|
||||
// 'favoutite_topic': data['favoutite_topic'],
|
||||
// 'knowledge_level': data['knowledge_level'],
|
||||
// 'profile_completed': data['profile_completed'],
|
||||
// 'preferred_language': data['preferred_language'],
|
||||
// 'language_challange': data['language_challange'],
|
||||
// 'profile_picture_url': data['profile_picture_url']
|
||||
// .toString()
|
||||
// .isNotEmpty
|
||||
// ? MultipartFile.fromFileSync(
|
||||
// data['profile_picture_url'],
|
||||
// filename:
|
||||
// data['profile_picture_url'].toString().split('/').last,
|
||||
// )
|
||||
// : null,
|
||||
// });
|
||||
// }
|
||||
// Response response = await _service.dio.put(
|
||||
// '$baseUrl/$kUserUrl',
|
||||
// data: formData,
|
||||
// );
|
||||
//
|
||||
// if (response.statusCode == 200) {
|
||||
// return {
|
||||
// 'status': ResponseStatus.success,
|
||||
// 'message': 'Profile updated successfully'
|
||||
// };
|
||||
// } else {
|
||||
// return {
|
||||
// 'status': ResponseStatus.failure,
|
||||
// 'message': 'Unknown Error Occurred'
|
||||
// };
|
||||
// }
|
||||
// } catch (e) {
|
||||
// return {
|
||||
// 'message': e.toString(),
|
||||
// 'status': ResponseStatus.failure,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// Assessments
|
||||
Future<List<Assessment>> getAssessments() async {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,20 @@ class ImageDownloaderService {
|
|||
final _service = locator<DioService>();
|
||||
|
||||
Future<String> downloader(String? networkImage) async {
|
||||
final Directory appDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
late File image;
|
||||
|
||||
late String profileImage;
|
||||
|
||||
final Directory appDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
if (networkImage != null) {
|
||||
profileImage = networkImage.contains('https://lh3.googleusercontent.com')
|
||||
? networkImage
|
||||
: '$kBaseUrl$networkImage';
|
||||
}
|
||||
|
||||
final Response profileImageResponse = await _service.dio.get(
|
||||
'$kBaseUrl$networkImage',
|
||||
profileImage,
|
||||
options: Options(
|
||||
followRedirects: false,
|
||||
responseType: ResponseType.bytes,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ String kVerifyOtpUrl = 'verify-otp';
|
|||
|
||||
String kResendOtpUrl = 'resend-otp';
|
||||
|
||||
String kResetPassword = 'resetPassword';
|
||||
|
||||
String kRequestResetCode = 'sendResetCode';
|
||||
|
||||
String kUpdateProfileImage = 'profile-picture';
|
||||
|
||||
String kRefreshTokenUrl = 'api/v1/auth/refresh';
|
||||
|
|
|
|||
|
|
@ -10,4 +10,15 @@ enum ProgressStatuses { pending, started, completed }
|
|||
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
||||
|
||||
// State object
|
||||
enum StateObjects { profileImage }
|
||||
enum StateObjects {
|
||||
verifyOtp,
|
||||
resendOtp,
|
||||
profileImage,
|
||||
registration,
|
||||
profileUpdate,
|
||||
resetPassword,
|
||||
loginWithEmail,
|
||||
loginWithGoogle,
|
||||
requestResetCode,
|
||||
profileCompletion,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,6 +216,12 @@ TextStyle style16DG600 = const TextStyle(
|
|||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
|
||||
TextStyle style18DG500 = const TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w500,
|
||||
);
|
||||
|
||||
TextStyle style18DG600 = const TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcDarkGrey,
|
||||
|
|
@ -234,6 +240,8 @@ TextStyle style14LG400 = const TextStyle(
|
|||
TextStyle style14MG400 = const TextStyle(
|
||||
color: kcMediumGrey,
|
||||
);
|
||||
TextStyle style14DG500 =
|
||||
const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500);
|
||||
|
||||
TextStyle style14DG400 = const TextStyle(
|
||||
color: kcDarkGrey,
|
||||
|
|
@ -274,24 +282,27 @@ Map<String, Style> htmlStyle = {
|
|||
Widget buildToastDescription(String message) => Text(
|
||||
message,
|
||||
maxLines: 4,
|
||||
style: const TextStyle(color: kcWhite, fontWeight: FontWeight.w500),
|
||||
style: const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500),
|
||||
);
|
||||
|
||||
void showErrorToast(String message) {
|
||||
toastification.show(
|
||||
showIcon: true,
|
||||
dragToClose: true,
|
||||
primaryColor: kcRed,
|
||||
showProgressBar: false,
|
||||
applyBlurEffect: false,
|
||||
icon: const Icon(Icons.check),
|
||||
alignment: Alignment.topCenter,
|
||||
primaryColor: kcBackgroundColor,
|
||||
type: ToastificationType.success,
|
||||
alignment: Alignment.bottomCenter,
|
||||
style: ToastificationStyle.fillColored,
|
||||
description: buildToastDescription(message),
|
||||
borderSide: const BorderSide(color: kcWhite),
|
||||
autoCloseDuration: const Duration(seconds: 5),
|
||||
autoCloseDuration: const Duration(seconds: 3),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||
borderSide: const BorderSide(color: kcPrimaryColor),
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: kcPrimaryColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -301,14 +312,17 @@ void showSuccessToast(String message) {
|
|||
dragToClose: true,
|
||||
showProgressBar: false,
|
||||
applyBlurEffect: false,
|
||||
icon: const Icon(Icons.check),
|
||||
primaryColor: kcPrimaryColor,
|
||||
alignment: Alignment.topCenter,
|
||||
primaryColor: kcBackgroundColor,
|
||||
type: ToastificationType.success,
|
||||
alignment: Alignment.bottomCenter,
|
||||
style: ToastificationStyle.fillColored,
|
||||
description: buildToastDescription(message),
|
||||
borderSide: const BorderSide(color: kcWhite),
|
||||
autoCloseDuration: const Duration(seconds: 5),
|
||||
autoCloseDuration: const Duration(seconds: 3),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||
borderSide: const BorderSide(color: kcPrimaryColor),
|
||||
icon: const Icon(
|
||||
Icons.check,
|
||||
color: kcPrimaryColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,11 +107,7 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
|||
|
||||
Widget _buildHeader(String title) => Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style18DG600,
|
||||
);
|
||||
|
||||
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ class AssessmentViewModel extends BaseViewModel {
|
|||
final _statusChecker = locator<StatusCheckerService>();
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// In-app navigation
|
||||
int _currentPage = 0;
|
||||
|
||||
int get currentPage => _currentPage;
|
||||
|
|
@ -255,16 +256,17 @@ class AssessmentViewModel extends BaseViewModel {
|
|||
|
||||
// Complete profile
|
||||
Future<void> completeProfile() async =>
|
||||
await runBusyFuture(_completeProfile());
|
||||
await runBusyFuture(_completeProfile(),
|
||||
busyObject: StateObjects.profileCompletion);
|
||||
|
||||
Future<void> _completeProfile() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
Map<String, dynamic> response =
|
||||
await _apiService.completeProfile(_userData);
|
||||
if (response['status'] == ResponseStatus.success) {
|
||||
showSuccessToast(response['message']);
|
||||
clearUserData();
|
||||
await replaceWithHome();
|
||||
showSuccessToast(response['message']);
|
||||
} else {
|
||||
showErrorToast(response['message']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,27 +61,23 @@ class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildIcon() => SvgPicture.asset(
|
||||
'assets/icons/complete.svg',
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Assessment complete!',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’re now analyzing your speaking skills',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
||||
|
|
@ -94,8 +90,8 @@ class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
borderRadius: 12,
|
||||
text: 'View My Results',
|
||||
onTap: () => viewModel.next(),
|
||||
foregroundColor: kcWhite,
|
||||
onTap: () => viewModel.next(),
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,25 +64,21 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildIcon() => SvgPicture.asset('assets/icons/alert.svg');
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'We didn’t get enough from your assessment',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||
|
|
@ -117,9 +113,9 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
height: 55,
|
||||
text: 'Skip',
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
borderColor: kcPrimaryColor,
|
||||
onTap: () => viewModel.next(),
|
||||
backgroundColor: kcWhite,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||
|
|
@ -69,7 +69,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Answer a few quick questions to help us understand your English proficiency.',
|
||||
style: style14MG400,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,5 @@ class AssessmentLoadingScreen extends StatelessWidget {
|
|||
|
||||
Widget _buildPageIndicator() => const PageLoadingIndicator();
|
||||
|
||||
Widget _buildRefreshButton() => RefreshButton(
|
||||
onTap: onTap,
|
||||
);
|
||||
Widget _buildRefreshButton() => RefreshButton(onTap: onTap);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceLarge,
|
||||
_buildTitle(viewModel),
|
||||
verticalSpaceSmall,
|
||||
_buildPrimarySubTitle(),
|
||||
_buildPrimarySubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildIconWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildSecondarySubTitle()
|
||||
_buildSecondarySubtitle()
|
||||
];
|
||||
|
||||
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
|
||||
|
|
@ -76,10 +76,10 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
textAlign: TextAlign.center,
|
||||
);
|
||||
|
||||
Widget _buildPrimarySubTitle() => const Text(
|
||||
Widget _buildPrimarySubtitle() => Text(
|
||||
'Great Job! Here’s your next step to keep improving.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
|
||||
|
|
@ -90,7 +90,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
Widget _buildIcon(AssessmentViewModel viewModel) => SvgPicture.asset(
|
||||
'assets/icons/${viewModel.proficiencyLevel.name.substring(0, 1)}_${viewModel.proficiencyLevel.name.substring(1)}.svg');
|
||||
|
||||
Widget _buildSecondarySubTitle() => Text(
|
||||
Widget _buildSecondarySubtitle() => Text(
|
||||
'Let\'s start your practice',
|
||||
style: style14DG400,
|
||||
textAlign: TextAlign.center,
|
||||
|
|
@ -113,8 +113,8 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
safe: false,
|
||||
text: 'Continue',
|
||||
borderRadius: 12,
|
||||
onTap: () => viewModel.next(),
|
||||
foregroundColor: kcWhite,
|
||||
onTap: () => viewModel.next(),
|
||||
backgroundColor: kcPrimaryColor,
|
||||
);
|
||||
|
||||
|
|
@ -127,10 +127,10 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
CustomElevatedButton(
|
||||
height: 55,
|
||||
borderRadius: 12,
|
||||
backgroundColor: kcWhite,
|
||||
text: 'Practice Speaking',
|
||||
borderColor: kcPrimaryColor,
|
||||
onTap: () => viewModel.next(),
|
||||
backgroundColor: kcWhite,
|
||||
foregroundColor: kcPrimaryColor,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class ResultAnalysisScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||
|
|
@ -61,19 +61,15 @@ class ResultAnalysisScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
'assets/icons/progress_indicator.svg',
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Analyzing your results…',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’re now analyzing your speaking skills',
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||
|
|
@ -72,20 +72,16 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
color: kcPrimaryColor,
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'We didn’t get enough from your assessment',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ',
|
||||
style: style14MG400,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
|
||||
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||
|
|
|
|||
|
|
@ -80,21 +80,25 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(viewModel),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg');
|
||||
|
||||
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
|
||||
TextSpan(text: 'Welcome aboard', style: style25DG600, children: [
|
||||
TextSpan(
|
||||
text: ', ${viewModel.userData['first_name']}!',
|
||||
text: 'Welcome aboard',
|
||||
style: style25DG600,
|
||||
)
|
||||
]),
|
||||
children: [
|
||||
TextSpan(
|
||||
style: style25DG600,
|
||||
text: ', ${viewModel.userData['first_name']}!',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'You’re ready to explore your personalized lessons.',
|
||||
style: style14MG400,
|
||||
);
|
||||
|
|
@ -115,5 +119,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
|||
);
|
||||
|
||||
Widget _buildState(AssessmentViewModel viewModel) =>
|
||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
||||
viewModel.busy(StateObjects.profileCompletion)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,14 +92,10 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
|
|||
Widget _buildIcon() =>
|
||||
const CircularIcon(icon: Icons.call, size: 50, color: kcPrimaryColor);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Call our support team between 9 AM - 6 PM',
|
||||
style: style25DG600,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle(String title) => Text(
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildEmptyTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildEmptySubTitle(),
|
||||
_buildEmptySubtitle(),
|
||||
];
|
||||
|
||||
Widget _buildEmptyIcon() => const Icon(
|
||||
|
|
@ -197,7 +197,7 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildEmptySubTitle() => const Text(
|
||||
Widget _buildEmptySubtitle() => const Text(
|
||||
'Start by exploring your learning materials and save them for offline access.',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
|
|
|
|||
142
lib/ui/views/forget_password/forget_password_view.dart
Normal file
142
lib/ui/views/forget_password/forget_password_view.dart
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked/stacked_annotations.dart';
|
||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.form.dart';
|
||||
import 'package:yimaru_app/ui/views/forget_password/screens/request_reset_code_screen.dart';
|
||||
import 'package:yimaru_app/ui/views/forget_password/screens/reset_password_screen.dart';
|
||||
|
||||
import '../../common/app_colors.dart';
|
||||
import '../../common/validators/form_validator.dart';
|
||||
import '../../widgets/large_app_bar.dart';
|
||||
import '../../widgets/page_loading_indicator.dart';
|
||||
import 'forget_password_viewmodel.dart';
|
||||
|
||||
@FormView(fields: [
|
||||
FormTextField(name: 'email', validator: FormValidator.validateEmail),
|
||||
FormTextField(name: 'resetCode', validator: FormValidator.validateForm),
|
||||
FormTextField(name: 'password', validator: FormValidator.validateForm),
|
||||
FormTextField(name: 'confirmPassword', validator: FormValidator.validateForm)
|
||||
])
|
||||
class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
|
||||
with $ForgetPasswordView {
|
||||
const ForgetPasswordView({Key? key}) : super(key: key);
|
||||
|
||||
void _initClearData() {
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
resetCodeController.clear();
|
||||
confirmPasswordController.clear();
|
||||
}
|
||||
|
||||
void _inAppPop(ForgetPasswordViewModel viewModel) {
|
||||
_clearDataOnNavigation(viewModel);
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) {
|
||||
if (viewModel.currentPage == 0) {
|
||||
emailController.clear();
|
||||
viewModel.resetRequestResetCodeScreen();
|
||||
} else {
|
||||
passwordController.clear();
|
||||
resetCodeController.clear();
|
||||
confirmPasswordController.clear();
|
||||
viewModel.resetResetPasswordScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void _pop({required bool value, required ForgetPasswordViewModel viewModel}) {
|
||||
{
|
||||
if (!value) return;
|
||||
_clearDataOnNavigation(viewModel);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onViewModelReady(ForgetPasswordViewModel viewModel) {
|
||||
_initClearData();
|
||||
syncFormWithViewModel(viewModel);
|
||||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
||||
@override
|
||||
ForgetPasswordViewModel viewModelBuilder(BuildContext context) =>
|
||||
ForgetPasswordViewModel();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
ForgetPasswordViewModel viewModel,
|
||||
Widget? child,
|
||||
) =>
|
||||
_buildLoginScreensWrapper(viewModel);
|
||||
|
||||
Widget _buildLoginScreensWrapper(ForgetPasswordViewModel viewModel) =>
|
||||
PopScope(
|
||||
canPop: true,
|
||||
onPopInvokedWithResult: (value, data) =>
|
||||
_pop(value: value, viewModel: viewModel),
|
||||
child: _buildScaffoldWrapper(viewModel));
|
||||
|
||||
Widget _buildScaffoldWrapper(ForgetPasswordViewModel viewModel) => Scaffold(
|
||||
backgroundColor: kcBackgroundColor,
|
||||
body: _buildScaffoldStack(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffoldStack(ForgetPasswordViewModel viewModel) =>
|
||||
Stack(children: [
|
||||
_buildScaffold(viewModel),
|
||||
_buildRequestResetCodeState(viewModel),
|
||||
_buildResetPasswordState(viewModel)
|
||||
]);
|
||||
|
||||
Widget _buildScaffold(ForgetPasswordViewModel viewModel) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildScaffoldChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildScaffoldChildren(ForgetPasswordViewModel viewModel) =>
|
||||
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
|
||||
|
||||
Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _inAppPop(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>
|
||||
Expanded(child: _buildBodyWrapper(viewModel));
|
||||
|
||||
Widget _buildBodyWrapper(ForgetPasswordViewModel viewModel) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: _buildBody(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildBody(ForgetPasswordViewModel viewModel) =>
|
||||
IndexedStack(index: viewModel.currentPage, children: _buildScreens());
|
||||
|
||||
List<Widget> _buildScreens() => [
|
||||
_buildRequestCodeScreen(),
|
||||
_buildResetPasswordScreen(),
|
||||
];
|
||||
|
||||
Widget _buildRequestCodeScreen() =>
|
||||
RequestCodeScreen(emailController: emailController);
|
||||
|
||||
Widget _buildResetPasswordScreen() => ResetPasswordScreen(
|
||||
passwordController: passwordController,
|
||||
resetCodeController: resetCodeController,
|
||||
confirmPasswordController: confirmPasswordController);
|
||||
|
||||
Widget _buildRequestResetCodeState(ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.requestResetCode)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
|
||||
Widget _buildResetPasswordState(ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.resetPassword)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
}
|
||||
281
lib/ui/views/forget_password/forget_password_view.form.dart
Normal file
281
lib/ui/views/forget_password/forget_password_view.form.dart
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
// **************************************************************************
|
||||
// StackedFormGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: public_member_api_docs, constant_identifier_names, non_constant_identifier_names,unnecessary_this
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
||||
|
||||
const bool _autoTextFieldValidation = true;
|
||||
|
||||
const String EmailValueKey = 'email';
|
||||
const String ResetCodeValueKey = 'resetCode';
|
||||
const String PasswordValueKey = 'password';
|
||||
const String ConfirmPasswordValueKey = 'confirmPassword';
|
||||
|
||||
final Map<String, TextEditingController>
|
||||
_ForgetPasswordViewTextEditingControllers = {};
|
||||
|
||||
final Map<String, FocusNode> _ForgetPasswordViewFocusNodes = {};
|
||||
|
||||
final Map<String, String? Function(String?)?>
|
||||
_ForgetPasswordViewTextValidations = {
|
||||
EmailValueKey: FormValidator.validateEmail,
|
||||
ResetCodeValueKey: FormValidator.validateForm,
|
||||
PasswordValueKey: FormValidator.validateForm,
|
||||
ConfirmPasswordValueKey: FormValidator.validateForm,
|
||||
};
|
||||
|
||||
mixin $ForgetPasswordView {
|
||||
TextEditingController get emailController =>
|
||||
_getFormTextEditingController(EmailValueKey);
|
||||
TextEditingController get resetCodeController =>
|
||||
_getFormTextEditingController(ResetCodeValueKey);
|
||||
TextEditingController get passwordController =>
|
||||
_getFormTextEditingController(PasswordValueKey);
|
||||
TextEditingController get confirmPasswordController =>
|
||||
_getFormTextEditingController(ConfirmPasswordValueKey);
|
||||
|
||||
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
|
||||
FocusNode get resetCodeFocusNode => _getFormFocusNode(ResetCodeValueKey);
|
||||
FocusNode get passwordFocusNode => _getFormFocusNode(PasswordValueKey);
|
||||
FocusNode get confirmPasswordFocusNode =>
|
||||
_getFormFocusNode(ConfirmPasswordValueKey);
|
||||
|
||||
TextEditingController _getFormTextEditingController(
|
||||
String key, {
|
||||
String? initialValue,
|
||||
}) {
|
||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(key)) {
|
||||
return _ForgetPasswordViewTextEditingControllers[key]!;
|
||||
}
|
||||
|
||||
_ForgetPasswordViewTextEditingControllers[key] =
|
||||
TextEditingController(text: initialValue);
|
||||
return _ForgetPasswordViewTextEditingControllers[key]!;
|
||||
}
|
||||
|
||||
FocusNode _getFormFocusNode(String key) {
|
||||
if (_ForgetPasswordViewFocusNodes.containsKey(key)) {
|
||||
return _ForgetPasswordViewFocusNodes[key]!;
|
||||
}
|
||||
_ForgetPasswordViewFocusNodes[key] = FocusNode();
|
||||
return _ForgetPasswordViewFocusNodes[key]!;
|
||||
}
|
||||
|
||||
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||
/// with the latest textController values
|
||||
void syncFormWithViewModel(FormStateHelper model) {
|
||||
emailController.addListener(() => _updateFormData(model));
|
||||
resetCodeController.addListener(() => _updateFormData(model));
|
||||
passwordController.addListener(() => _updateFormData(model));
|
||||
confirmPasswordController.addListener(() => _updateFormData(model));
|
||||
|
||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||
}
|
||||
|
||||
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||
/// with the latest textController values
|
||||
@Deprecated(
|
||||
'Use syncFormWithViewModel instead.'
|
||||
'This feature was deprecated after 3.1.0.',
|
||||
)
|
||||
void listenToFormUpdated(FormViewModel model) {
|
||||
emailController.addListener(() => _updateFormData(model));
|
||||
resetCodeController.addListener(() => _updateFormData(model));
|
||||
passwordController.addListener(() => _updateFormData(model));
|
||||
confirmPasswordController.addListener(() => _updateFormData(model));
|
||||
|
||||
_updateFormData(model, forceValidate: _autoTextFieldValidation);
|
||||
}
|
||||
|
||||
/// Updates the formData on the FormViewModel
|
||||
void _updateFormData(FormStateHelper model, {bool forceValidate = false}) {
|
||||
model.setData(
|
||||
model.formValueMap
|
||||
..addAll({
|
||||
EmailValueKey: emailController.text,
|
||||
ResetCodeValueKey: resetCodeController.text,
|
||||
PasswordValueKey: passwordController.text,
|
||||
ConfirmPasswordValueKey: confirmPasswordController.text,
|
||||
}),
|
||||
);
|
||||
|
||||
if (_autoTextFieldValidation || forceValidate) {
|
||||
updateValidationData(model);
|
||||
}
|
||||
}
|
||||
|
||||
bool validateFormFields(FormViewModel model) {
|
||||
_updateFormData(model, forceValidate: true);
|
||||
return model.isFormValid;
|
||||
}
|
||||
|
||||
/// Calls dispose on all the generated controllers and focus nodes
|
||||
void disposeForm() {
|
||||
// The dispose function for a TextEditingController sets all listeners to null
|
||||
|
||||
for (var controller in _ForgetPasswordViewTextEditingControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
for (var focusNode in _ForgetPasswordViewFocusNodes.values) {
|
||||
focusNode.dispose();
|
||||
}
|
||||
|
||||
_ForgetPasswordViewTextEditingControllers.clear();
|
||||
_ForgetPasswordViewFocusNodes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
extension ValueProperties on FormStateHelper {
|
||||
bool get hasAnyValidationMessage => this
|
||||
.fieldsValidationMessages
|
||||
.values
|
||||
.any((validation) => validation != null);
|
||||
|
||||
bool get isFormValid {
|
||||
if (!_autoTextFieldValidation) this.validateForm();
|
||||
|
||||
return !hasAnyValidationMessage;
|
||||
}
|
||||
|
||||
String? get emailValue => this.formValueMap[EmailValueKey] as String?;
|
||||
String? get resetCodeValue => this.formValueMap[ResetCodeValueKey] as String?;
|
||||
String? get passwordValue => this.formValueMap[PasswordValueKey] as String?;
|
||||
String? get confirmPasswordValue =>
|
||||
this.formValueMap[ConfirmPasswordValueKey] as String?;
|
||||
|
||||
set emailValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({EmailValueKey: value}),
|
||||
);
|
||||
|
||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(EmailValueKey)) {
|
||||
_ForgetPasswordViewTextEditingControllers[EmailValueKey]?.text =
|
||||
value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
set resetCodeValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({ResetCodeValueKey: value}),
|
||||
);
|
||||
|
||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(
|
||||
ResetCodeValueKey)) {
|
||||
_ForgetPasswordViewTextEditingControllers[ResetCodeValueKey]?.text =
|
||||
value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
set passwordValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({PasswordValueKey: value}),
|
||||
);
|
||||
|
||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(
|
||||
PasswordValueKey)) {
|
||||
_ForgetPasswordViewTextEditingControllers[PasswordValueKey]?.text =
|
||||
value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
set confirmPasswordValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({ConfirmPasswordValueKey: value}),
|
||||
);
|
||||
|
||||
if (_ForgetPasswordViewTextEditingControllers.containsKey(
|
||||
ConfirmPasswordValueKey)) {
|
||||
_ForgetPasswordViewTextEditingControllers[ConfirmPasswordValueKey]?.text =
|
||||
value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasEmail =>
|
||||
this.formValueMap.containsKey(EmailValueKey) &&
|
||||
(emailValue?.isNotEmpty ?? false);
|
||||
bool get hasResetCode =>
|
||||
this.formValueMap.containsKey(ResetCodeValueKey) &&
|
||||
(resetCodeValue?.isNotEmpty ?? false);
|
||||
bool get hasPassword =>
|
||||
this.formValueMap.containsKey(PasswordValueKey) &&
|
||||
(passwordValue?.isNotEmpty ?? false);
|
||||
bool get hasConfirmPassword =>
|
||||
this.formValueMap.containsKey(ConfirmPasswordValueKey) &&
|
||||
(confirmPasswordValue?.isNotEmpty ?? false);
|
||||
|
||||
bool get hasEmailValidationMessage =>
|
||||
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
|
||||
bool get hasResetCodeValidationMessage =>
|
||||
this.fieldsValidationMessages[ResetCodeValueKey]?.isNotEmpty ?? false;
|
||||
bool get hasPasswordValidationMessage =>
|
||||
this.fieldsValidationMessages[PasswordValueKey]?.isNotEmpty ?? false;
|
||||
bool get hasConfirmPasswordValidationMessage =>
|
||||
this.fieldsValidationMessages[ConfirmPasswordValueKey]?.isNotEmpty ??
|
||||
false;
|
||||
|
||||
String? get emailValidationMessage =>
|
||||
this.fieldsValidationMessages[EmailValueKey];
|
||||
String? get resetCodeValidationMessage =>
|
||||
this.fieldsValidationMessages[ResetCodeValueKey];
|
||||
String? get passwordValidationMessage =>
|
||||
this.fieldsValidationMessages[PasswordValueKey];
|
||||
String? get confirmPasswordValidationMessage =>
|
||||
this.fieldsValidationMessages[ConfirmPasswordValueKey];
|
||||
}
|
||||
|
||||
extension Methods on FormStateHelper {
|
||||
setEmailValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[EmailValueKey] = validationMessage;
|
||||
setResetCodeValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[ResetCodeValueKey] = validationMessage;
|
||||
setPasswordValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[PasswordValueKey] = validationMessage;
|
||||
setConfirmPasswordValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[ConfirmPasswordValueKey] =
|
||||
validationMessage;
|
||||
|
||||
/// Clears text input fields on the Form
|
||||
void clearForm() {
|
||||
emailValue = '';
|
||||
resetCodeValue = '';
|
||||
passwordValue = '';
|
||||
confirmPasswordValue = '';
|
||||
}
|
||||
|
||||
/// Validates text input fields on the Form
|
||||
void validateForm() {
|
||||
this.setValidationMessages({
|
||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the validation message for the given key
|
||||
String? getValidationMessage(String key) {
|
||||
final validatorForKey = _ForgetPasswordViewTextValidations[key];
|
||||
if (validatorForKey == null) return null;
|
||||
|
||||
String? validationMessageForKey = validatorForKey(
|
||||
_ForgetPasswordViewTextEditingControllers[key]!.text,
|
||||
);
|
||||
|
||||
return validationMessageForKey;
|
||||
}
|
||||
|
||||
/// Updates the fieldsValidationMessages on the FormViewModel
|
||||
void updateValidationData(FormStateHelper model) =>
|
||||
model.setValidationMessages({
|
||||
EmailValueKey: getValidationMessage(EmailValueKey),
|
||||
ResetCodeValueKey: getValidationMessage(ResetCodeValueKey),
|
||||
PasswordValueKey: getValidationMessage(PasswordValueKey),
|
||||
ConfirmPasswordValueKey: getValidationMessage(ConfirmPasswordValueKey),
|
||||
});
|
||||
231
lib/ui/views/forget_password/forget_password_viewmodel.dart
Normal file
231
lib/ui/views/forget_password/forget_password_viewmodel.dart
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:yimaru_app/ui/views/login/login_view.dart';
|
||||
|
||||
import '../../../app/app.locator.dart';
|
||||
import '../../../services/api_service.dart';
|
||||
import '../../../services/status_checker_service.dart';
|
||||
import '../../common/enmus.dart';
|
||||
import '../../common/ui_helpers.dart';
|
||||
|
||||
class ForgetPasswordViewModel extends FormViewModel {
|
||||
final _apiService = locator<ApiService>();
|
||||
|
||||
final _statusChecker = locator<StatusCheckerService>();
|
||||
|
||||
final _navigationService = locator<NavigationService>();
|
||||
|
||||
// User data
|
||||
final Map<String, dynamic> _userData = {};
|
||||
|
||||
Map<String, dynamic> get userData => _userData;
|
||||
|
||||
// Navigation
|
||||
int _currentPage = 0;
|
||||
|
||||
int get currentPage => _currentPage;
|
||||
|
||||
// Email
|
||||
bool _focusEmail = false;
|
||||
|
||||
bool get focusEmail => _focusEmail;
|
||||
|
||||
// Reset code
|
||||
bool _focusResetCode = false;
|
||||
|
||||
bool get focusResetCode => _focusResetCode;
|
||||
|
||||
// Password
|
||||
bool _length = false;
|
||||
|
||||
bool get length => _length;
|
||||
|
||||
bool _number = false;
|
||||
|
||||
bool get number => _number;
|
||||
|
||||
bool _specialChar = false;
|
||||
|
||||
bool get specialChar => _specialChar;
|
||||
|
||||
bool _focusPassword = false;
|
||||
|
||||
bool get focusPassword => _focusPassword;
|
||||
|
||||
bool _obscurePassword = true;
|
||||
|
||||
bool get obscurePassword => _obscurePassword;
|
||||
|
||||
bool _passwordMatch = false;
|
||||
|
||||
bool get passwordMatch => _passwordMatch;
|
||||
|
||||
// Confirm password
|
||||
bool _focusConfirmPassword = false;
|
||||
|
||||
bool get focusConfirmPassword => _focusConfirmPassword;
|
||||
|
||||
bool _obscureConfirmPassword = true;
|
||||
|
||||
bool get obscureConfirmPassword => _obscureConfirmPassword;
|
||||
|
||||
// Add user data
|
||||
void addUserData(Map<String, dynamic> data) {
|
||||
_userData.addAll(data);
|
||||
}
|
||||
|
||||
void clearUserData() {
|
||||
_userData.clear();
|
||||
}
|
||||
|
||||
// Email
|
||||
void setEmailFocus() {
|
||||
_focusEmail = true;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset code
|
||||
void setResetCodeFocus() {
|
||||
_focusResetCode = true;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Password
|
||||
void setPasswordFocus() {
|
||||
_focusPassword = true;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
void validatePassword(
|
||||
{required String password, required String confirmPassword}) {
|
||||
if (password.length > 8) {
|
||||
_length = true;
|
||||
} else {
|
||||
_length = false;
|
||||
}
|
||||
|
||||
if (RegExp(r'\d').hasMatch(password)) {
|
||||
_number = true;
|
||||
} else {
|
||||
_number = false;
|
||||
}
|
||||
|
||||
if (RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password)) {
|
||||
_specialChar = true;
|
||||
} else {
|
||||
_specialChar = false;
|
||||
}
|
||||
|
||||
if (password == confirmPassword) {
|
||||
_passwordMatch = true;
|
||||
} else {
|
||||
_passwordMatch = false;
|
||||
}
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
double validationProgress() {
|
||||
int completed = 0;
|
||||
|
||||
if (_length) completed++;
|
||||
if (_number) completed++;
|
||||
if (_specialChar) completed++;
|
||||
if (_passwordMatch) completed++;
|
||||
|
||||
return completed / 4; // returns 0.0 → 1.0
|
||||
}
|
||||
|
||||
void setObscurePassword() {
|
||||
_obscurePassword = !_obscurePassword;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Confirm password
|
||||
void setConfirmPasswordFocus() {
|
||||
_focusConfirmPassword = true;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
void setObscureConfirmPassword() {
|
||||
_obscureConfirmPassword = !_obscureConfirmPassword;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Form reset
|
||||
|
||||
// Reset reset password screen
|
||||
void resetResetPasswordScreen() {
|
||||
_length = false;
|
||||
_number = false;
|
||||
_specialChar = false;
|
||||
_passwordMatch = false;
|
||||
_focusPassword = false;
|
||||
_focusResetCode = false;
|
||||
_focusConfirmPassword = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset reset password screen
|
||||
void resetRequestResetCodeScreen() {
|
||||
_focusEmail = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// In-app navigation
|
||||
void goTo(int page) {
|
||||
_currentPage = page;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
void goBack() {
|
||||
if (_currentPage == 1) {
|
||||
_currentPage = 0;
|
||||
rebuildUi();
|
||||
} else {
|
||||
_navigationService.back();
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
void pop() => _navigationService.back();
|
||||
|
||||
Future<void> replaceWithLogin() async =>
|
||||
await _navigationService.clearStackAndShowView(const LoginView());
|
||||
|
||||
// Remote api calls
|
||||
|
||||
// Request reset code
|
||||
Future<void> requestResetCode() async =>
|
||||
await runBusyFuture(_requestResetCode(),
|
||||
busyObject: StateObjects.requestResetCode);
|
||||
|
||||
Future<void> _requestResetCode() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
Map<String, dynamic> response =
|
||||
await _apiService.requestResetCode(_userData);
|
||||
if (response['status'] == ResponseStatus.success) {
|
||||
goTo(1);
|
||||
showSuccessToast(response['message']);
|
||||
} else {
|
||||
showErrorToast(response['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request reset code
|
||||
Future<void> resetPassword() async => await runBusyFuture(_resetPassword(),
|
||||
busyObject: StateObjects.resetPassword);
|
||||
|
||||
Future<void> _resetPassword() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
Map<String, dynamic> response =
|
||||
await _apiService.resetPassword(_userData);
|
||||
if (response['status'] == ResponseStatus.success) {
|
||||
showSuccessToast(response['message']);
|
||||
await replaceWithLogin();
|
||||
} else {
|
||||
showErrorToast(response['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/views/forget_password/forget_password_viewmodel.dart';
|
||||
import 'package:yimaru_app/ui/widgets/login_account.dart';
|
||||
|
||||
import '../../../common/app_colors.dart';
|
||||
import '../../../common/ui_helpers.dart';
|
||||
import '../../../widgets/custom_elevated_button.dart';
|
||||
import '../../../widgets/option_text_divider.dart';
|
||||
import '../forget_password_view.form.dart';
|
||||
|
||||
class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
||||
final TextEditingController emailController;
|
||||
|
||||
const RequestCodeScreen({
|
||||
super.key,
|
||||
required this.emailController,
|
||||
});
|
||||
|
||||
Future<void> _addUserData(ForgetPasswordViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
Map<String, dynamic> data = {
|
||||
'email': emailController.text,
|
||||
};
|
||||
viewModel.addUserData(data);
|
||||
|
||||
await viewModel.requestResetCode();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ForgetPasswordViewModel viewModel) =>
|
||||
_buildBody(viewModel);
|
||||
|
||||
Widget _buildBody(ForgetPasswordViewModel viewModel) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: _buildBodyChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildBodyChildren(ForgetPasswordViewModel viewModel) =>
|
||||
[_buildColumnScroller(viewModel), _buildContinueButtonWrapper(viewModel)];
|
||||
|
||||
Widget _buildColumnScroller(ForgetPasswordViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildUpperColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildUpperColumn(ForgetPasswordViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildUpperColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildUpperColumnChildren(ForgetPasswordViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceLarge,
|
||||
_buildEmailFormField(viewModel),
|
||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
||||
verticalSpaceTiny,
|
||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
||||
_buildEmailValidatorWrapper(viewModel),
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Reset password',
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Enter your email, we will send you a reset code.',
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildEmailFormField(ForgetPasswordViewModel viewModel) =>
|
||||
TextFormField(
|
||||
controller: emailController,
|
||||
onTap: viewModel.setEmailFocus,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: inputDecoration(
|
||||
hint: 'Email',
|
||||
focus: viewModel.focusEmail,
|
||||
filled: emailController.text.isNotEmpty),
|
||||
);
|
||||
|
||||
Widget _buildEmailValidatorWrapper(ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.hasEmailValidationMessage
|
||||
? _buildEmailValidator(viewModel)
|
||||
: Container();
|
||||
|
||||
Widget _buildEmailValidator(ForgetPasswordViewModel viewModel) => Text(
|
||||
viewModel.emailValidationMessage!,
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(ForgetPasswordViewModel viewModel) =>
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50),
|
||||
child: _buildContinueButton(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildContinueButton(ForgetPasswordViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
height: 55,
|
||||
text: 'Continue',
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
onTap: emailController.text.isNotEmpty &&
|
||||
!viewModel.hasEmailValidationMessage
|
||||
? () => _addUserData(viewModel)
|
||||
: null,
|
||||
backgroundColor: emailController.text.isNotEmpty &&
|
||||
!viewModel.hasEmailValidationMessage
|
||||
? kcPrimaryColor
|
||||
: kcPrimaryColor.withOpacity(0.1),
|
||||
);
|
||||
}
|
||||
244
lib/ui/views/forget_password/screens/reset_password_screen.dart
Normal file
244
lib/ui/views/forget_password/screens/reset_password_screen.dart
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
import '../../../common/app_colors.dart';
|
||||
import '../../../common/ui_helpers.dart';
|
||||
import '../../../widgets/custom_elevated_button.dart';
|
||||
import '../../../widgets/custom_form_label.dart';
|
||||
import '../../../widgets/custom_linear_progress_indicator.dart';
|
||||
import '../../../widgets/obscure_password.dart';
|
||||
import '../../../widgets/validator_list_tile.dart';
|
||||
import '../forget_password_viewmodel.dart';
|
||||
import '../forget_password_view.form.dart';
|
||||
|
||||
class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
|
||||
final TextEditingController resetCodeController;
|
||||
final TextEditingController passwordController;
|
||||
final TextEditingController confirmPasswordController;
|
||||
|
||||
const ResetPasswordScreen(
|
||||
{super.key,
|
||||
required this.resetCodeController,
|
||||
required this.passwordController,
|
||||
required this.confirmPasswordController});
|
||||
|
||||
Future<void> _reset(ForgetPasswordViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
Map<String, dynamic> data = {
|
||||
'otp': resetCodeController.text,
|
||||
'password': passwordController.text,
|
||||
};
|
||||
viewModel.addUserData(data);
|
||||
|
||||
await viewModel.resetPassword();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ForgetPasswordViewModel viewModel) =>
|
||||
_buildBodyChildren(viewModel);
|
||||
|
||||
Widget _buildBodyChildren(ForgetPasswordViewModel viewModel) =>
|
||||
SingleChildScrollView(
|
||||
child: _buildBodyColumn(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildBodyColumn(ForgetPasswordViewModel viewModel) => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _buildBodyColumnChildren(viewModel),
|
||||
);
|
||||
|
||||
List<Widget> _buildBodyColumnChildren(ForgetPasswordViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildFormLabel('Reset code'),
|
||||
verticalSpaceSmall,
|
||||
_buildResetCodeFormField(viewModel),
|
||||
if (viewModel.hasResetCodeValidationMessage && viewModel.focusResetCode)
|
||||
verticalSpaceTiny,
|
||||
if (viewModel.hasResetCodeValidationMessage && viewModel.focusResetCode)
|
||||
_buildResetCodeValidationWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildFormLabel('New Password'),
|
||||
verticalSpaceSmall,
|
||||
_buildPasswordFormField(viewModel),
|
||||
if (viewModel.hasPasswordValidationMessage && viewModel.focusPassword)
|
||||
verticalSpaceTiny,
|
||||
if (viewModel.hasPasswordValidationMessage && viewModel.focusPassword)
|
||||
_buildPasswordValidationWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildFormLabel('Confirm Password'),
|
||||
verticalSpaceSmall,
|
||||
_buildConfirmPasswordFormField(viewModel),
|
||||
if (viewModel.hasConfirmPasswordValidationMessage &&
|
||||
viewModel.focusConfirmPassword)
|
||||
verticalSpaceTiny,
|
||||
if (viewModel.hasConfirmPasswordValidationMessage &&
|
||||
viewModel.focusConfirmPassword)
|
||||
_buildConfirmPasswordValidationWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildLinearProgressIndicator(viewModel),
|
||||
verticalSpaceSmall,
|
||||
_buildCharLengthValidator(viewModel),
|
||||
_buildNumberValidator(viewModel),
|
||||
_buildSymbolValidator(viewModel),
|
||||
_buildPasswordMatchValidator(viewModel),
|
||||
verticalSpaceSmall,
|
||||
_buildSignUpButton(viewModel),
|
||||
verticalSpaceMedium
|
||||
];
|
||||
|
||||
Widget _buildTitle() => Text(
|
||||
'Reset password',
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildFormLabel(String label) => CustomFormLabel(
|
||||
label: label,
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildResetCodeFormField(ForgetPasswordViewModel viewModel) =>
|
||||
TextFormField(
|
||||
controller: resetCodeController,
|
||||
onTap: viewModel.setResetCodeFocus,
|
||||
decoration: inputDecoration(
|
||||
hint: 'Reset code',
|
||||
focus: viewModel.focusResetCode,
|
||||
filled: passwordController.text.isNotEmpty),
|
||||
);
|
||||
|
||||
Widget _buildResetCodeValidationWrapper(ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.hasResetCodeValidationMessage
|
||||
? _buildResetCodeValidator(viewModel)
|
||||
: Container();
|
||||
|
||||
Widget _buildResetCodeValidator(ForgetPasswordViewModel viewModel) => Text(
|
||||
viewModel.resetCodeValidationMessage!,
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildPasswordFormField(ForgetPasswordViewModel viewModel) =>
|
||||
TextFormField(
|
||||
controller: passwordController,
|
||||
onTap: viewModel.setPasswordFocus,
|
||||
obscureText: viewModel.obscurePassword,
|
||||
decoration: inputDecoration(
|
||||
hint: 'Password',
|
||||
focus: viewModel.focusPassword,
|
||||
suffix: _buildObscurePassword(viewModel),
|
||||
filled: passwordController.text.isNotEmpty),
|
||||
onChanged: (value) => viewModel.validatePassword(
|
||||
password: passwordController.text,
|
||||
confirmPassword: confirmPasswordController.text),
|
||||
);
|
||||
|
||||
Widget _buildObscurePassword(ForgetPasswordViewModel viewModel) =>
|
||||
ObscurePassword(
|
||||
focus: viewModel.focusPassword,
|
||||
obscure: viewModel.obscurePassword,
|
||||
onTap: viewModel.setObscurePassword,
|
||||
);
|
||||
|
||||
Widget _buildPasswordValidationWrapper(ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.hasPasswordValidationMessage
|
||||
? _buildPasswordValidator(viewModel)
|
||||
: Container();
|
||||
|
||||
Widget _buildPasswordValidator(ForgetPasswordViewModel viewModel) => Text(
|
||||
viewModel.passwordValidationMessage!,
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildConfirmPasswordFormField(ForgetPasswordViewModel viewModel) =>
|
||||
TextFormField(
|
||||
controller: confirmPasswordController,
|
||||
onTap: viewModel.setConfirmPasswordFocus,
|
||||
obscureText: viewModel.obscureConfirmPassword,
|
||||
onChanged: (value) => viewModel.validatePassword(
|
||||
password: passwordController.text,
|
||||
confirmPassword: confirmPasswordController.text),
|
||||
decoration: inputDecoration(
|
||||
hint: 'Confirm Password',
|
||||
focus: viewModel.focusConfirmPassword,
|
||||
suffix: _buildObscureConfirmPassword(viewModel),
|
||||
filled: confirmPasswordController.text.isNotEmpty),
|
||||
);
|
||||
|
||||
Widget _buildObscureConfirmPassword(ForgetPasswordViewModel viewModel) =>
|
||||
ObscurePassword(
|
||||
focus: viewModel.focusConfirmPassword,
|
||||
obscure: viewModel.obscureConfirmPassword,
|
||||
onTap: viewModel.setObscureConfirmPassword,
|
||||
);
|
||||
|
||||
Widget _buildConfirmPasswordValidationWrapper(
|
||||
ForgetPasswordViewModel viewModel) =>
|
||||
viewModel.hasConfirmPasswordValidationMessage
|
||||
? _buildConfirmPasswordValidator(viewModel)
|
||||
: Container();
|
||||
|
||||
Widget _buildConfirmPasswordValidator(ForgetPasswordViewModel viewModel) =>
|
||||
Text(
|
||||
viewModel.confirmPasswordValidationMessage!,
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildLinearProgressIndicator(ForgetPasswordViewModel viewModel) =>
|
||||
CustomLinearProgressIndicator(
|
||||
activeColor: kcPrimaryColor,
|
||||
backgroundColor: kcVeryLightGrey,
|
||||
progress: viewModel.validationProgress(),
|
||||
);
|
||||
|
||||
Widget _buildCharLengthValidator(ForgetPasswordViewModel viewModel) =>
|
||||
ValidatorListTile(
|
||||
backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey,
|
||||
label: '8 characters minimum');
|
||||
|
||||
Widget _buildNumberValidator(ForgetPasswordViewModel viewModel) =>
|
||||
ValidatorListTile(
|
||||
backgroundColor: viewModel.number ? kcPrimaryColor : kcLightGrey,
|
||||
label: 'a number');
|
||||
|
||||
Widget _buildSymbolValidator(ForgetPasswordViewModel viewModel) =>
|
||||
ValidatorListTile(
|
||||
backgroundColor: viewModel.specialChar ? kcPrimaryColor : kcLightGrey,
|
||||
label: 'one symbol minimum');
|
||||
|
||||
Widget _buildPasswordMatchValidator(ForgetPasswordViewModel viewModel) =>
|
||||
ValidatorListTile(
|
||||
backgroundColor:
|
||||
viewModel.passwordMatch ? kcPrimaryColor : kcLightGrey,
|
||||
label: 'password match');
|
||||
|
||||
Widget _buildSignUpButton(ForgetPasswordViewModel viewModel) =>
|
||||
CustomElevatedButton(
|
||||
height: 55,
|
||||
text: 'Continue',
|
||||
borderRadius: 12,
|
||||
foregroundColor: kcWhite,
|
||||
onTap: passwordController.text.isNotEmpty &&
|
||||
confirmPasswordController.text.isNotEmpty &&
|
||||
resetCodeController.text.isNotEmpty &&
|
||||
viewModel.number &&
|
||||
viewModel.length &&
|
||||
viewModel.specialChar &&
|
||||
viewModel.specialChar &&
|
||||
viewModel.passwordMatch
|
||||
? () async => await _reset(viewModel)
|
||||
: null,
|
||||
backgroundColor: passwordController.text.isNotEmpty &&
|
||||
confirmPasswordController.text.isNotEmpty &&
|
||||
resetCodeController.text.isNotEmpty &&
|
||||
viewModel.number &&
|
||||
viewModel.length &&
|
||||
viewModel.specialChar &&
|
||||
viewModel.specialChar &&
|
||||
viewModel.passwordMatch
|
||||
? kcPrimaryColor
|
||||
: kcPrimaryColor.withOpacity(0.1),
|
||||
);
|
||||
}
|
||||
|
|
@ -117,6 +117,7 @@ class HomeViewModel extends ReactiveViewModel {
|
|||
response = {'data': true, 'status': ResponseStatus.success};
|
||||
}
|
||||
|
||||
|
||||
if (response['status'] == ResponseStatus.success && !response['data']) {
|
||||
await replaceWithOnboarding();
|
||||
} else if (response['status'] == ResponseStatus.success &&
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildLanguages(viewModel)
|
||||
];
|
||||
|
|
@ -72,22 +72,18 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
|||
);
|
||||
|
||||
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
|
||||
title: 'Language Preference',
|
||||
onTap: viewModel.pop,
|
||||
title: 'Language Preference',
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Choose your language',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'You can switch languages anytime',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildLanguages(LanguageViewModel viewModel) => ListView.builder(
|
||||
|
|
|
|||
|
|
@ -73,20 +73,14 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
|||
_buildListView(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'A1 - Beginner',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcPrimaryColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style18P600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubTitle() => Text(
|
||||
'Your Current Level',
|
||||
style: TextStyle(
|
||||
color: kcDarkGrey,
|
||||
),
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildOverallProgress() => const OverallLearnProgress();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked/stacked_annotations.dart';
|
||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/views/login/screens/login_otp_screen.dart';
|
||||
import 'package:yimaru_app/ui/views/login/screens/login_with_email_screen.dart';
|
||||
import 'package:yimaru_app/ui/views/login/screens/login_with_phone_number_screen.dart';
|
||||
|
|
@ -24,10 +25,18 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
|||
|
||||
@override
|
||||
void onViewModelReady(LoginViewModel viewModel) {
|
||||
_clearData();
|
||||
syncFormWithViewModel(viewModel);
|
||||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
||||
void _clearData() {
|
||||
otpController.clear();
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
phoneNumberController.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
LoginViewModel viewModelBuilder(BuildContext context) => LoginViewModel();
|
||||
|
||||
|
|
@ -52,8 +61,11 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
|||
body: _buildScaffoldStack(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffoldStack(LoginViewModel viewModel) =>
|
||||
Stack(children: [_buildScaffold(viewModel), _buildBusyLogin(viewModel)]);
|
||||
Widget _buildScaffoldStack(LoginViewModel viewModel) => Stack(children: [
|
||||
_buildScaffold(viewModel),
|
||||
_buildLoginWithEmailState(viewModel),
|
||||
_buildLoginWithGoogleState(viewModel)
|
||||
]);
|
||||
|
||||
Widget _buildScaffold(LoginViewModel viewModel) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -94,6 +106,13 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
|||
otpController: otpController,
|
||||
phoneNumberController: phoneNumberController);
|
||||
|
||||
Widget _buildBusyLogin(LoginViewModel viewModel) =>
|
||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
||||
Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.loginWithEmail)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
|
||||
Widget _buildLoginWithGoogleState(LoginViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.loginWithGoogle)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,13 +138,17 @@ class LoginViewModel extends FormViewModel {
|
|||
Future<void> navigateToRegister() async =>
|
||||
await _navigationService.navigateToRegisterView();
|
||||
|
||||
Future<void> navigateToForgetPassword() async =>
|
||||
await _navigationService.navigateToForgetPasswordView();
|
||||
|
||||
Future<void> replaceWithHome() async =>
|
||||
await _navigationService.clearStackAndShowView(const HomeView());
|
||||
|
||||
// Remote api calls
|
||||
|
||||
// Login with email
|
||||
Future<void> emailLogin() async => await runBusyFuture(_emailLogin());
|
||||
Future<void> emailLogin() async => await runBusyFuture(_emailLogin(),
|
||||
busyObject: StateObjects.loginWithEmail);
|
||||
|
||||
Future<void> _emailLogin() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
@ -167,7 +171,8 @@ class LoginViewModel extends FormViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> googleLogin() async => await runBusyFuture(_googleLogin());
|
||||
Future<void> googleLogin() async => await runBusyFuture(_googleLogin(),
|
||||
busyObject: StateObjects.loginWithGoogle);
|
||||
|
||||
Future<void> _googleLogin() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubTitleWrapper(viewModel),
|
||||
_buildSubtitleWrapper(viewModel),
|
||||
verticalSpaceLarge,
|
||||
_buildEmailFormField(viewModel),
|
||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
||||
|
|
@ -71,19 +71,15 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
verticalSpaceTiny,
|
||||
if (viewModel.hasPasswordValidationMessage && viewModel.focusPassword)
|
||||
_buildPasswordValidationWrapper(viewModel),
|
||||
_buildForgetPasswordTextButtonWrapper(),
|
||||
_buildForgetPasswordTextButtonWrapper(viewModel),
|
||||
];
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Welcome Back',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||
Widget _buildSubtitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||
onTap: () async => await viewModel.navigateToRegister(),
|
||||
);
|
||||
|
||||
|
|
@ -104,11 +100,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
|
||||
Widget _buildEmailValidator(LoginViewModel viewModel) => Text(
|
||||
viewModel.emailValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildPasswordFormField(LoginViewModel viewModel) => TextFormField(
|
||||
|
|
@ -135,26 +127,23 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
|
||||
Widget _buildPasswordValidator(LoginViewModel viewModel) => Text(
|
||||
viewModel.passwordValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildForgetPasswordTextButtonWrapper() => Align(
|
||||
Widget _buildForgetPasswordTextButtonWrapper(LoginViewModel viewModel) =>
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: _buildForgetPasswordTextButton(),
|
||||
child: _buildForgetPasswordTextButton(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildForgetPasswordTextButton() => TextButton(
|
||||
onPressed: () {},
|
||||
Widget _buildForgetPasswordTextButton(LoginViewModel viewModel) => TextButton(
|
||||
onPressed: () async => await viewModel.navigateToForgetPassword(),
|
||||
child: _buildForgetPasswordText(),
|
||||
);
|
||||
|
||||
Widget _buildForgetPasswordText() => const Text(
|
||||
Widget _buildForgetPasswordText() => Text(
|
||||
'Forget Password?',
|
||||
style: TextStyle(color: kcPrimaryColor),
|
||||
style: style14P400,
|
||||
);
|
||||
|
||||
Widget _buildLowerColumn(LoginViewModel viewModel) => Column(
|
||||
|
|
@ -207,8 +196,8 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
|||
backgroundColor: kcWhite,
|
||||
leadingIcon: Icons.phone,
|
||||
borderColor: kcPrimaryColor,
|
||||
onTap: () => viewModel.goTo(1),
|
||||
foregroundColor: kcPrimaryColor,
|
||||
text: 'Login with Phone Number',
|
||||
onTap: () => viewModel.goTo(1),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
|||
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubTitleWrapper(viewModel),
|
||||
_buildSubtitleWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
|
|
@ -57,22 +57,18 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
|||
_buildPhoneNumberValidatorWrapper(viewModel),
|
||||
];
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Welcome Back',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||
Widget _buildSubtitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||
onTap: () async => await viewModel.navigateToRegister(),
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Enter your phone number. We will send you a confirmation code there',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildPhoneNumberWrapper(LoginViewModel viewModel) => Row(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import 'onboarding_viewmodel.dart';
|
|||
import 'onboarding_view.form.dart';
|
||||
|
||||
@FormView(fields: [
|
||||
FormTextField(name: 'answer', validator: FormValidator.validateForm),
|
||||
FormTextField(name: 'fullName', validator: FormValidator.validateForm),
|
||||
FormTextField(name: 'challenge', validator: FormValidator.validateForm),
|
||||
FormTextField(name: 'occupation', validator: FormValidator.validateForm),
|
||||
|
|
@ -30,13 +29,55 @@ class OnboardingView extends StackedView<OnboardingViewModel>
|
|||
with $OnboardingView {
|
||||
const OnboardingView({Key? key}) : super(key: key);
|
||||
|
||||
void _initFormFields() {
|
||||
answerController.text = 'Book';
|
||||
void _initClearData() {
|
||||
topicController.clear();
|
||||
fullNameController.clear();
|
||||
challengeController.clear();
|
||||
occupationController.clear();
|
||||
languageGoalController.clear();
|
||||
}
|
||||
|
||||
void _clearDataOnNavigation(OnboardingViewModel viewModel) {
|
||||
if (viewModel.currentPage == 0) {
|
||||
fullNameController.clear();
|
||||
viewModel.resetFullNameFormScreen();
|
||||
} else if (viewModel.currentPage == 1) {
|
||||
viewModel.resetGenderFormScreen();
|
||||
} else if (viewModel.currentPage == 2) {
|
||||
viewModel.resetBirthdayFormScreen();
|
||||
} else if (viewModel.currentPage == 3) {
|
||||
viewModel.resetAgeGroupFormScreen();
|
||||
} else if (viewModel.currentPage == 4) {
|
||||
viewModel.resetEducationalBackgroundFormScreen();
|
||||
} else if (viewModel.currentPage == 5) {
|
||||
occupationController.clear();
|
||||
viewModel.resetOccupationFormScreen();
|
||||
} else if (viewModel.currentPage == 6) {
|
||||
viewModel.resetCountryRegionFormScreen();
|
||||
} else if (viewModel.currentPage == 7) {
|
||||
viewModel.resetLearningGoalFormScreen();
|
||||
} else if (viewModel.currentPage == 8) {
|
||||
languageGoalController.clear();
|
||||
viewModel.resetLanguageGoalFormScreen();
|
||||
} else if (viewModel.currentPage == 9) {
|
||||
challengeController.clear();
|
||||
viewModel.resetChallengeFormScreen();
|
||||
} else if (viewModel.currentPage == 10) {
|
||||
topicController.clear();
|
||||
viewModel.resetTopicFormScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
{
|
||||
_clearDataOnNavigation(viewModel);
|
||||
viewModel.goBack();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onViewModelReady(OnboardingViewModel viewModel) {
|
||||
_initFormFields();
|
||||
_initClearData();
|
||||
syncFormWithViewModel(viewModel);
|
||||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
|
@ -58,7 +99,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
|
|||
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
|
||||
PopScope(
|
||||
canPop: viewModel.currentPage == 0 ? true : false,
|
||||
onPopInvokedWithResult: (value, data) => viewModel.pop(),
|
||||
onPopInvokedWithResult: (value, data) => _pop(viewModel),
|
||||
child: _buildOnboardingScreens(viewModel));
|
||||
|
||||
Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
|||
|
||||
const bool _autoTextFieldValidation = true;
|
||||
|
||||
const String AnswerValueKey = 'answer';
|
||||
const String FullNameValueKey = 'fullName';
|
||||
const String ChallengeValueKey = 'challenge';
|
||||
const String OccupationValueKey = 'occupation';
|
||||
|
|
@ -25,7 +24,6 @@ final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
|
|||
final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
|
||||
|
||||
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
|
||||
AnswerValueKey: FormValidator.validateForm,
|
||||
FullNameValueKey: FormValidator.validateForm,
|
||||
ChallengeValueKey: FormValidator.validateForm,
|
||||
OccupationValueKey: FormValidator.validateForm,
|
||||
|
|
@ -34,8 +32,6 @@ final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
|
|||
};
|
||||
|
||||
mixin $OnboardingView {
|
||||
TextEditingController get answerController =>
|
||||
_getFormTextEditingController(AnswerValueKey);
|
||||
TextEditingController get fullNameController =>
|
||||
_getFormTextEditingController(FullNameValueKey);
|
||||
TextEditingController get challengeController =>
|
||||
|
|
@ -47,7 +43,6 @@ mixin $OnboardingView {
|
|||
TextEditingController get topicController =>
|
||||
_getFormTextEditingController(TopicValueKey);
|
||||
|
||||
FocusNode get answerFocusNode => _getFormFocusNode(AnswerValueKey);
|
||||
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
|
||||
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
|
||||
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
|
||||
|
|
@ -79,7 +74,6 @@ mixin $OnboardingView {
|
|||
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||
/// with the latest textController values
|
||||
void syncFormWithViewModel(FormStateHelper model) {
|
||||
answerController.addListener(() => _updateFormData(model));
|
||||
fullNameController.addListener(() => _updateFormData(model));
|
||||
challengeController.addListener(() => _updateFormData(model));
|
||||
occupationController.addListener(() => _updateFormData(model));
|
||||
|
|
@ -96,7 +90,6 @@ mixin $OnboardingView {
|
|||
'This feature was deprecated after 3.1.0.',
|
||||
)
|
||||
void listenToFormUpdated(FormViewModel model) {
|
||||
answerController.addListener(() => _updateFormData(model));
|
||||
fullNameController.addListener(() => _updateFormData(model));
|
||||
challengeController.addListener(() => _updateFormData(model));
|
||||
occupationController.addListener(() => _updateFormData(model));
|
||||
|
|
@ -111,7 +104,6 @@ mixin $OnboardingView {
|
|||
model.setData(
|
||||
model.formValueMap
|
||||
..addAll({
|
||||
AnswerValueKey: answerController.text,
|
||||
FullNameValueKey: fullNameController.text,
|
||||
ChallengeValueKey: challengeController.text,
|
||||
OccupationValueKey: occupationController.text,
|
||||
|
|
@ -158,7 +150,6 @@ extension ValueProperties on FormStateHelper {
|
|||
return !hasAnyValidationMessage;
|
||||
}
|
||||
|
||||
String? get answerValue => this.formValueMap[AnswerValueKey] as String?;
|
||||
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
|
||||
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
|
||||
String? get occupationValue =>
|
||||
|
|
@ -167,16 +158,6 @@ extension ValueProperties on FormStateHelper {
|
|||
this.formValueMap[LanguageGoalValueKey] as String?;
|
||||
String? get topicValue => this.formValueMap[TopicValueKey] as String?;
|
||||
|
||||
set answerValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({AnswerValueKey: value}),
|
||||
);
|
||||
|
||||
if (_OnboardingViewTextEditingControllers.containsKey(AnswerValueKey)) {
|
||||
_OnboardingViewTextEditingControllers[AnswerValueKey]?.text = value ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
set fullNameValue(String? value) {
|
||||
this.setData(
|
||||
this.formValueMap..addAll({FullNameValueKey: value}),
|
||||
|
|
@ -232,9 +213,6 @@ extension ValueProperties on FormStateHelper {
|
|||
}
|
||||
}
|
||||
|
||||
bool get hasAnswer =>
|
||||
this.formValueMap.containsKey(AnswerValueKey) &&
|
||||
(answerValue?.isNotEmpty ?? false);
|
||||
bool get hasFullName =>
|
||||
this.formValueMap.containsKey(FullNameValueKey) &&
|
||||
(fullNameValue?.isNotEmpty ?? false);
|
||||
|
|
@ -251,8 +229,6 @@ extension ValueProperties on FormStateHelper {
|
|||
this.formValueMap.containsKey(TopicValueKey) &&
|
||||
(topicValue?.isNotEmpty ?? false);
|
||||
|
||||
bool get hasAnswerValidationMessage =>
|
||||
this.fieldsValidationMessages[AnswerValueKey]?.isNotEmpty ?? false;
|
||||
bool get hasFullNameValidationMessage =>
|
||||
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
|
||||
bool get hasChallengeValidationMessage =>
|
||||
|
|
@ -264,8 +240,6 @@ extension ValueProperties on FormStateHelper {
|
|||
bool get hasTopicValidationMessage =>
|
||||
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
|
||||
|
||||
String? get answerValidationMessage =>
|
||||
this.fieldsValidationMessages[AnswerValueKey];
|
||||
String? get fullNameValidationMessage =>
|
||||
this.fieldsValidationMessages[FullNameValueKey];
|
||||
String? get challengeValidationMessage =>
|
||||
|
|
@ -279,8 +253,6 @@ extension ValueProperties on FormStateHelper {
|
|||
}
|
||||
|
||||
extension Methods on FormStateHelper {
|
||||
setAnswerValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[AnswerValueKey] = validationMessage;
|
||||
setFullNameValidationMessage(String? validationMessage) =>
|
||||
this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
|
||||
setChallengeValidationMessage(String? validationMessage) =>
|
||||
|
|
@ -294,7 +266,6 @@ extension Methods on FormStateHelper {
|
|||
|
||||
/// Clears text input fields on the Form
|
||||
void clearForm() {
|
||||
answerValue = '';
|
||||
fullNameValue = '';
|
||||
challengeValue = '';
|
||||
occupationValue = '';
|
||||
|
|
@ -305,7 +276,6 @@ extension Methods on FormStateHelper {
|
|||
/// Validates text input fields on the Form
|
||||
void validateForm() {
|
||||
this.setValidationMessages({
|
||||
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
||||
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
||||
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
||||
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
||||
|
|
@ -330,7 +300,6 @@ String? getValidationMessage(String key) {
|
|||
/// Updates the fieldsValidationMessages on the FormViewModel
|
||||
void updateValidationData(FormStateHelper model) =>
|
||||
model.setValidationMessages({
|
||||
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
||||
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
||||
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
||||
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
||||
|
|
|
|||
|
|
@ -372,16 +372,83 @@ class OnboardingViewModel extends FormViewModel {
|
|||
_userData.clear();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
Future<void> navigateToLanguage() async =>
|
||||
await _navigationService.navigateToLanguageView();
|
||||
// Form reset
|
||||
|
||||
Future<void> navigateToAssessment() async =>
|
||||
await _navigationService.navigateToAssessmentView(data: _userData);
|
||||
// Reset full name form screen
|
||||
void resetFullNameFormScreen() {
|
||||
_focusFullName = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
Future<void> replaceWithHome() async =>
|
||||
await _navigationService.clearStackAndShowView(const HomeView());
|
||||
// Reset gender form screen
|
||||
void resetGenderFormScreen() {
|
||||
_selectedGender = null;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset birthday form screen
|
||||
void resetBirthdayFormScreen() {
|
||||
_selectedBirthday = null;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset age group form screen
|
||||
void resetAgeGroupFormScreen() {
|
||||
_selectedAgeGroup = null;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset educational background form screen
|
||||
void resetEducationalBackgroundFormScreen() {
|
||||
_selectedEducationalBackground = null;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset occupation form screen
|
||||
void resetOccupationFormScreen() {
|
||||
_focusOccupation = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset country region form screen
|
||||
void resetCountryRegionFormScreen() {
|
||||
_selectedCountry = 'Ethiopia';
|
||||
_selectedRegion = 'Addis Ababa';
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset learning goal form screen
|
||||
void resetLearningGoalFormScreen() {
|
||||
_selectedLearningGoal = null;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset language goal form screen
|
||||
void resetLanguageGoalFormScreen() {
|
||||
_focusLanguageGoal = false;
|
||||
_selectedLanguageGoal = null;
|
||||
_showLanguageGoalTextBox = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset challenge form screen
|
||||
void resetChallengeFormScreen() {
|
||||
_focusChallenge = false;
|
||||
_selectedChallenge = null;
|
||||
_showChallengeTextBox = false;
|
||||
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset topic form screen
|
||||
void resetTopicFormScreen() {
|
||||
_focusTopic = false;
|
||||
_selectedTopic = null;
|
||||
_showTopicTextBox = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// In-app navigation
|
||||
void next({int? page}) async {
|
||||
if (page == null) {
|
||||
if (_previousPage != 0) {
|
||||
|
|
@ -396,7 +463,7 @@ class OnboardingViewModel extends FormViewModel {
|
|||
rebuildUi();
|
||||
}
|
||||
|
||||
void pop() {
|
||||
void goBack() {
|
||||
if (_currentPage == 0) {
|
||||
_navigationService.back();
|
||||
} else {
|
||||
|
|
@ -405,4 +472,14 @@ class OnboardingViewModel extends FormViewModel {
|
|||
rebuildUi();
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
Future<void> navigateToLanguage() async =>
|
||||
await _navigationService.navigateToLanguageView();
|
||||
|
||||
Future<void> navigateToAssessment() async =>
|
||||
await _navigationService.navigateToAssessmentView(data: _userData);
|
||||
|
||||
Future<void> replaceWithHome() async =>
|
||||
await _navigationService.clearStackAndShowView(const HomeView());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
|||
class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||
const AgeGroupFormScreen({super.key});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetAgeGroupFormScreen();
|
||||
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -74,24 +80,20 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Which age range are you in?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubTitle() => Text(
|
||||
'We’ll personalize your learning experience based on your age.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14DG400,
|
||||
);
|
||||
|
||||
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ import '../../../widgets/birthday_selector.dart';
|
|||
class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||
const BirthdayFormScreen({super.key});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetBirthdayFormScreen();
|
||||
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -64,30 +70,26 @@ class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildBirthdayFormField(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Pick your birthday?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’ll personalize your learning experience based on your birthday.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
const ChallengeFormScreen({super.key, required this.challengeController});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
challengeController.clear();
|
||||
viewModel.resetChallengeFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -74,7 +80,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildChallenges(viewModel),
|
||||
if (viewModel.showChallengeTextBox) _buildChallengeFormField(viewModel),
|
||||
|
|
@ -90,24 +96,20 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'What challenge do you face most with English?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Everyone has struggles, let’s start fixing yours 😊',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildChallenges(OnboardingViewModel viewModel) => ListView.builder(
|
||||
|
|
@ -151,11 +153,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildChallengeValidator(OnboardingViewModel viewModel) => Text(
|
||||
viewModel.challengeValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
|||
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||
const CountryRegionFormScreen({super.key});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetCountryRegionFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -71,7 +76,7 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildCountryDropDown(viewModel),
|
||||
verticalSpaceMedium,
|
||||
|
|
@ -80,24 +85,20 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Where are you from?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Select your country and region from the dropdown',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ class EducationalBackgroundFormScreen
|
|||
extends ViewModelWidget<OnboardingViewModel> {
|
||||
const EducationalBackgroundFormScreen({super.key});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetEducationalBackgroundFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -77,9 +82,9 @@ class EducationalBackgroundFormScreen
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceLarge,
|
||||
_buildFullNameFormField(viewModel),
|
||||
if (viewModel.hasFullNameValidationMessage && viewModel.focusFullName)
|
||||
|
|
@ -96,7 +96,7 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => const Text(
|
||||
'We’ll use your name to personalize your learning journey.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,11 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
|||
class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||
const GenderFormScreen({super.key});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetGenderFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -63,30 +68,26 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildAgeGroups(viewModel)
|
||||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Choose your gender?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’ll personalize your learning experience based on your gender.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
const LanguageGoalFormScreen(
|
||||
{super.key, required this.languageGoalController});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
languageGoalController.clear();
|
||||
viewModel.resetLanguageGoalFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -75,7 +81,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildReasons(viewModel),
|
||||
if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
|
||||
|
|
@ -91,26 +97,20 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'What’s your main goal for improving your English?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your goal helps us tailor your learning journey.',
|
||||
style: TextStyle(
|
||||
color: kcMediumGrey,
|
||||
),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
|
||||
|
|
@ -154,11 +154,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
|
||||
viewModel.languageGoalValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
return Icons.book;
|
||||
}
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
viewModel.resetLearningGoalFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -87,9 +92,9 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
];
|
||||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
onPop: viewModel.pop,
|
||||
showBackButton: true,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
const OccupationFormScreen({super.key, required this.occupationController});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
occupationController.clear();
|
||||
viewModel.resetOccupationFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -71,7 +77,7 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceLarge,
|
||||
_buildOccupationFormField(viewModel),
|
||||
if (viewModel.hasOccupationValidationMessage &&
|
||||
|
|
@ -84,23 +90,19 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: true,
|
||||
onPop: viewModel.pop,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'What’s your occupation?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'We’ll personalize your learning experience based on your occupation.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildOccupationFormField(OnboardingViewModel viewModel) =>
|
||||
|
|
@ -120,11 +122,7 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text(
|
||||
viewModel.occupationValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
const TopicFormScreen({super.key, required this.topicController});
|
||||
|
||||
void _pop(OnboardingViewModel viewModel) {
|
||||
topicController.clear();
|
||||
viewModel.resetTopicFormScreen();
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
|
|
@ -45,8 +51,8 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: true,
|
||||
onPop: viewModel.pop,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _pop(viewModel),
|
||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||
);
|
||||
|
||||
|
|
@ -82,7 +88,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle(),
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
_buildTopics(viewModel),
|
||||
if (viewModel.showTopicTextBox) _buildTopicFormField(viewModel),
|
||||
|
|
@ -97,18 +103,14 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
verticalSpaceMedium,
|
||||
];
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Which topics interest you most?',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Your favorite topics help us create fun, relatable lessons.',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildTopics(OnboardingViewModel viewModel) => ListView.builder(
|
||||
|
|
@ -150,11 +152,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
|||
|
||||
Widget _buildTopicValidator(OnboardingViewModel viewModel) => Text(
|
||||
viewModel.topicValidationMessage!,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
style: style12R700,
|
||||
);
|
||||
|
||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||
|
|
|
|||
|
|
@ -164,28 +164,28 @@ class ProfileView extends StackedView<ProfileViewModel> {
|
|||
Widget _buildDownloadsCard(ProfileViewModel viewModel) => ProfileCard(
|
||||
icon: Icons.download,
|
||||
title: 'My Downloads',
|
||||
subTitle: 'Access offline lessons and saved videos',
|
||||
subtitle: 'Access offline lessons and saved videos',
|
||||
onTap: () async => await viewModel.navigateToDownloads(),
|
||||
);
|
||||
|
||||
Widget _buildProgressCard(ProfileViewModel viewModel) => ProfileCard(
|
||||
title: 'My Progress',
|
||||
icon: Icons.stacked_bar_chart,
|
||||
subTitle: 'Track your achievements and learning streak',
|
||||
subtitle: 'Track your achievements and learning streak',
|
||||
onTap: () async => await viewModel.navigateToProgress(),
|
||||
);
|
||||
|
||||
Widget _buildAccountCard(ProfileViewModel viewModel) => ProfileCard(
|
||||
title: 'Account & Privacy',
|
||||
icon: Icons.privacy_tip_outlined,
|
||||
subTitle: 'Manage setting and app preference',
|
||||
subtitle: 'Manage setting and app preference',
|
||||
onTap: () async => await viewModel.navigateToAccountPrivacy(),
|
||||
);
|
||||
|
||||
Widget _buildSupportCard(ProfileViewModel viewModel) => ProfileCard(
|
||||
title: 'Support',
|
||||
icon: Icons.headphones,
|
||||
subTitle: 'Get help through phone or Telegram',
|
||||
subtitle: 'Get help through phone or Telegram',
|
||||
onTap: () async => await viewModel.navigateToSupport(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -700,5 +700,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
|
|||
);
|
||||
|
||||
Widget _buildState(ProfileDetailViewModel viewModel) =>
|
||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
||||
viewModel.busy(StateObjects.profileUpdate)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,7 +220,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
|
|||
}
|
||||
|
||||
// Update profile
|
||||
Future<void> updateProfile() async => await runBusyFuture(_updateProfile());
|
||||
Future<void> updateProfile() async => await runBusyFuture(_updateProfile(),
|
||||
busyObject: StateObjects.profileUpdate);
|
||||
|
||||
Future<void> _updateProfile() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
@ -228,8 +229,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
|
|||
await _apiService.completeProfile(_userData);
|
||||
if (response['status'] == ResponseStatus.success) {
|
||||
await _authenticationService.updateUserData(_userData);
|
||||
showSuccessToast(response['message']);
|
||||
pop();
|
||||
showSuccessToast(response['message']);
|
||||
} else {
|
||||
showErrorToast(response['message']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
|||
title: viewModel.progresses[index]['title'],
|
||||
color: viewModel.progresses[index]['color'],
|
||||
status: viewModel.progresses[index]['status'],
|
||||
subTitle: viewModel.progresses[index]['subTitle'],
|
||||
subtitle: viewModel.progresses[index]['subtitle'],
|
||||
isCompleted: viewModel.progresses[index]['isCompleted'],
|
||||
),
|
||||
);
|
||||
|
|
@ -111,7 +111,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
|||
required String title,
|
||||
required String icon,
|
||||
required String status,
|
||||
required String subTitle,
|
||||
required String subtitle,
|
||||
required bool isCompleted,
|
||||
required ProgressViewModel viewModel}) =>
|
||||
CourseLevelCard(
|
||||
|
|
@ -119,7 +119,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
|||
title: title,
|
||||
color: color,
|
||||
status: status,
|
||||
subTitle: subTitle,
|
||||
subtitle: subtitle,
|
||||
isCompleted: isCompleted,
|
||||
onTap: viewModel.navigateToOngoingProgress,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ProgressViewModel extends BaseViewModel {
|
|||
'isCompleted': true,
|
||||
'status': 'Completed',
|
||||
'icon': 'assets/icons/b_1.svg',
|
||||
'subTitle': 'You’ve mastered everyday English basics!',
|
||||
'subtitle': 'You’ve mastered everyday English basics!',
|
||||
},
|
||||
{
|
||||
'title': 'Elementary',
|
||||
|
|
@ -23,7 +23,7 @@ class ProgressViewModel extends BaseViewModel {
|
|||
'status': 'In Progress',
|
||||
'color': kcPrimaryColor,
|
||||
'icon': 'assets/icons/b_1.svg',
|
||||
'subTitle': 'Continue improving your conversations and fluency.',
|
||||
'subtitle': 'Continue improving your conversations and fluency.',
|
||||
},
|
||||
{
|
||||
'title': 'Beginner',
|
||||
|
|
@ -31,7 +31,7 @@ class ProgressViewModel extends BaseViewModel {
|
|||
'status': 'In Progress',
|
||||
'color': kcPrimaryColor,
|
||||
'icon': 'assets/icons/b_1.svg',
|
||||
'subTitle': 'You’ve mastered everyday English basics!',
|
||||
'subtitle': 'You’ve mastered everyday English basics!',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked/stacked_annotations.dart';
|
||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
||||
import 'package:yimaru_app/ui/views/register/screens/create_password_screen.dart';
|
||||
import 'package:yimaru_app/ui/views/register/screens/register_with_email_screen.dart';
|
||||
import 'package:yimaru_app/ui/views/register/screens/register_with_phone_number_screen.dart';
|
||||
|
|
@ -24,8 +25,46 @@ import 'register_view.form.dart';
|
|||
class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||
const RegisterView({Key? key}) : super(key: key);
|
||||
|
||||
void _initClearData() {
|
||||
otpController.clear();
|
||||
emailController.clear();
|
||||
passwordController.clear();
|
||||
phoneNumberController.clear();
|
||||
confirmPasswordController.clear();
|
||||
}
|
||||
|
||||
void _inAppPop(RegisterViewModel viewModel) {
|
||||
print('OnPop');
|
||||
print(viewModel.currentPage);
|
||||
_clearDataOnNavigation(viewModel);
|
||||
viewModel.goBack();
|
||||
}
|
||||
|
||||
void _clearDataOnNavigation(RegisterViewModel viewModel) {
|
||||
if (viewModel.currentPage == 0) {
|
||||
emailController.clear();
|
||||
viewModel.resetRegisterWithEmailScreen();
|
||||
} else if (viewModel.currentPage == 2) {
|
||||
passwordController.clear();
|
||||
confirmPasswordController.clear();
|
||||
viewModel.resetCreatePasswordScreen();
|
||||
} else if (viewModel.currentPage == 3) {
|
||||
otpController.clear();
|
||||
viewModel.resetRegistrationOtpScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void _pop({required bool value, required RegisterViewModel viewModel}) {
|
||||
{
|
||||
if (!value) return;
|
||||
_clearDataOnNavigation(viewModel);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onViewModelReady(RegisterViewModel viewModel) {
|
||||
_initClearData();
|
||||
syncFormWithViewModel(viewModel);
|
||||
super.onViewModelReady(viewModel);
|
||||
}
|
||||
|
|
@ -44,10 +83,8 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
|||
|
||||
Widget _buildRegisterScreensWrapper(RegisterViewModel viewModel) => PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (value, data) {
|
||||
if (value) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack());
|
||||
},
|
||||
onPopInvokedWithResult: (value, data) =>
|
||||
_pop(value: value, viewModel: viewModel),
|
||||
child: _buildScaffoldWrapper(viewModel));
|
||||
|
||||
Widget _buildScaffoldWrapper(RegisterViewModel viewModel) => Scaffold(
|
||||
|
|
@ -55,8 +92,11 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
|||
body: _buildScaffoldStack(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack(
|
||||
children: [_buildScaffold(viewModel), _buildBusyRegistration(viewModel)]);
|
||||
Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack(children: [
|
||||
_buildScaffold(viewModel),
|
||||
_buildRegistrationState(viewModel),
|
||||
_buildVerityOtpState(viewModel)
|
||||
]);
|
||||
|
||||
Widget _buildScaffold(RegisterViewModel viewModel) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
@ -68,8 +108,8 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
|||
|
||||
Widget _buildAppBar(RegisterViewModel viewModel) => LargeAppBar(
|
||||
showBackButton: true,
|
||||
onPop: viewModel.goBack,
|
||||
showLanguageSelection: true,
|
||||
onPop: () => _inAppPop(viewModel),
|
||||
);
|
||||
|
||||
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
||||
|
|
@ -81,7 +121,7 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
|||
);
|
||||
|
||||
Widget _buildBody(RegisterViewModel viewModel) =>
|
||||
IndexedStack(index: viewModel.currentIndex, children: _buildScreens());
|
||||
IndexedStack(index: viewModel.currentPage, children: _buildScreens());
|
||||
|
||||
List<Widget> _buildScreens() => [
|
||||
_buildRegisterWithEmailScreen(),
|
||||
|
|
@ -106,6 +146,13 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
|||
passwordController: passwordController,
|
||||
confirmPasswordController: confirmPasswordController);
|
||||
|
||||
Widget _buildBusyRegistration(RegisterViewModel viewModel) =>
|
||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
||||
Widget _buildRegistrationState(RegisterViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.registration)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
|
||||
Widget _buildVerityOtpState(RegisterViewModel viewModel) =>
|
||||
viewModel.busy(StateObjects.verifyOtp)
|
||||
? const PageLoadingIndicator()
|
||||
: Container();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ class RegisterViewModel extends FormViewModel {
|
|||
final _authenticationService = locator<AuthenticationService>();
|
||||
|
||||
// Navigation
|
||||
int _currentIndex = 0;
|
||||
int _currentPage = 0;
|
||||
|
||||
int get currentIndex => _currentIndex;
|
||||
int get currentPage => _currentPage;
|
||||
|
||||
// Email
|
||||
bool _focusEmail = false;
|
||||
|
|
@ -213,9 +213,35 @@ class RegisterViewModel extends FormViewModel {
|
|||
_userData.clear();
|
||||
}
|
||||
|
||||
// Form reset
|
||||
|
||||
// Reset register with email screen
|
||||
void resetRegisterWithEmailScreen() {
|
||||
_focusEmail = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset create password screen
|
||||
void resetCreatePasswordScreen() {
|
||||
_agree = false;
|
||||
_length = false;
|
||||
_number = false;
|
||||
_specialChar = false;
|
||||
_passwordMatch = false;
|
||||
_focusPassword = false;
|
||||
_focusConfirmPassword = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// Reset registration otp screen
|
||||
void resetRegistrationOtpScreen() {
|
||||
_focusOtp = false;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
// In-app navigation
|
||||
void goTo({required int page, RegistrationType? type}) {
|
||||
_currentIndex = page;
|
||||
_currentPage = page;
|
||||
if (type != null) {
|
||||
_registrationType = type;
|
||||
}
|
||||
|
|
@ -223,17 +249,17 @@ class RegisterViewModel extends FormViewModel {
|
|||
}
|
||||
|
||||
void goBack() {
|
||||
if (_currentIndex == 1) {
|
||||
_currentIndex = 0;
|
||||
if (_currentPage == 1) {
|
||||
_currentPage = 0;
|
||||
rebuildUi();
|
||||
} else if (_currentIndex == 2) {
|
||||
_currentIndex = 0;
|
||||
} else if (_currentPage == 2) {
|
||||
_currentPage = 0;
|
||||
rebuildUi();
|
||||
} else if (_currentIndex == 3) {
|
||||
} else if (_currentPage == 3) {
|
||||
if (_registrationType == RegistrationType.phone) {
|
||||
_currentIndex = 1;
|
||||
_currentPage = 1;
|
||||
} else {
|
||||
_currentIndex = 2;
|
||||
_currentPage = 2;
|
||||
}
|
||||
|
||||
rebuildUi();
|
||||
|
|
@ -258,7 +284,8 @@ class RegisterViewModel extends FormViewModel {
|
|||
// Remote api calls
|
||||
|
||||
// Register
|
||||
Future<void> register() async => await runBusyFuture(_register());
|
||||
Future<void> register() async =>
|
||||
await runBusyFuture(_register(), busyObject: StateObjects.registration);
|
||||
|
||||
Future<void> _register() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
@ -273,7 +300,8 @@ class RegisterViewModel extends FormViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> verifyOtp() async => await runBusyFuture(_verifyOtp());
|
||||
Future<void> verifyOtp() async =>
|
||||
await runBusyFuture(_verifyOtp(), busyObject: StateObjects.verifyOtp);
|
||||
|
||||
Future<void> _verifyOtp() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
@ -296,7 +324,8 @@ class RegisterViewModel extends FormViewModel {
|
|||
}
|
||||
|
||||
// Resend otp
|
||||
Future<void> resendOtp() async => await runBusyFuture(_resendOtp());
|
||||
Future<void> resendOtp() async =>
|
||||
await runBusyFuture(_resendOtp(), busyObject: StateObjects.resendOtp);
|
||||
|
||||
Future<void> _resendOtp() async {
|
||||
if (await _statusChecker.checkConnection()) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubTitleWrapper(viewModel),
|
||||
_buildSubtitleWrapper(viewModel),
|
||||
verticalSpaceLarge,
|
||||
_buildEmailFormField(viewModel),
|
||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
||||
|
|
@ -73,7 +73,7 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
),
|
||||
);
|
||||
|
||||
Widget _buildSubTitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
||||
Widget _buildSubtitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
||||
onTap: () async => await viewModel.replaceToLogin(),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
||||
verticalSpaceMedium,
|
||||
_buildTitle(),
|
||||
_buildSubTitleWrapper(viewModel),
|
||||
_buildSubtitleWrapper(viewModel),
|
||||
verticalSpaceMedium,
|
||||
_buildSubtitle(),
|
||||
verticalSpaceMedium,
|
||||
|
|
@ -57,22 +57,18 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
_buildPhoneNumberValidatorWrapper(viewModel),
|
||||
];
|
||||
|
||||
Widget _buildTitle() => const Text(
|
||||
Widget _buildTitle() => Text(
|
||||
'Create an Account',
|
||||
style: TextStyle(
|
||||
fontSize: 25,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style25DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
||||
Widget _buildSubtitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
||||
onTap: () async => await viewModel.replaceToLogin(),
|
||||
);
|
||||
|
||||
Widget _buildSubtitle() => const Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Enter your phone number. We will send you a confirmation code there',
|
||||
style: TextStyle(color: kcMediumGrey),
|
||||
style: style14MG400,
|
||||
);
|
||||
|
||||
Widget _buildPhoneNumberWrapper(RegisterViewModel viewModel) => Row(
|
||||
|
|
|
|||
|
|
@ -126,6 +126,8 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
|
|||
|
||||
Widget _buildResendButton(RegisterViewModel viewModel) => TextButton(
|
||||
onPressed: () async => await viewModel.resendOtp(),
|
||||
style:
|
||||
const ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
|
||||
child: _buildResendText());
|
||||
|
||||
Widget _buildResendText() => Text(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
final String icon;
|
||||
final String title;
|
||||
final String status;
|
||||
final String subTitle;
|
||||
final String subtitle;
|
||||
final bool isCompleted;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
required this.title,
|
||||
required this.color,
|
||||
required this.status,
|
||||
required this.subTitle,
|
||||
required this.subtitle,
|
||||
required this.isCompleted,
|
||||
});
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ class CourseLevelCard extends StatelessWidget {
|
|||
|
||||
Widget _buildSubTitle() => Expanded(
|
||||
child: Text(
|
||||
subTitle,
|
||||
subtitle,
|
||||
maxLines: 3,
|
||||
style: const TextStyle(color: kcMediumGrey),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||
|
||||
class CustomLargeRadioButton extends StatelessWidget {
|
||||
final String title;
|
||||
|
|
@ -49,7 +50,7 @@ class CustomLargeRadioButton extends StatelessWidget {
|
|||
);
|
||||
|
||||
List<Widget> _buildButtonRowChildren() =>
|
||||
[_buildIconSectionWrapper(), _buildTitle(), _buildSubTitle()];
|
||||
[_buildIconSectionWrapper(), _buildTitle(), _buildSubtitle()];
|
||||
|
||||
Widget _buildIconSectionWrapper() => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
|
@ -67,14 +68,10 @@ class CustomLargeRadioButton extends StatelessWidget {
|
|||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
style: style18DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
subtitle,
|
||||
style: const TextStyle(color: kcMediumGrey),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class LearnAppBar extends StatelessWidget {
|
|||
);
|
||||
|
||||
List<Widget> _buildGreetingChildren() =>
|
||||
[_buildGreetingTitle(), _buildSubTitle()];
|
||||
[_buildGreetingTitle(), _buildSubtitle()];
|
||||
|
||||
Widget _buildGreetingTitle() => Text.rich(
|
||||
TextSpan(text: 'Hello,', style: style14DG600, children: [
|
||||
|
|
@ -77,7 +77,7 @@ class LearnAppBar extends StatelessWidget {
|
|||
]),
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
Widget _buildSubtitle() => Text(
|
||||
'Ready to keep learning English today?',
|
||||
textAlign: TextAlign.center,
|
||||
style: style14DG400,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
|||
class ProfileCard extends StatelessWidget {
|
||||
final String title;
|
||||
final IconData icon;
|
||||
final String subTitle;
|
||||
final String subtitle;
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
const ProfileCard(
|
||||
|
|
@ -13,7 +13,7 @@ class ProfileCard extends StatelessWidget {
|
|||
this.onTap,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.subTitle});
|
||||
required this.subtitle});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _buildContainerWrapper();
|
||||
|
|
@ -43,7 +43,7 @@ class ProfileCard extends StatelessWidget {
|
|||
verticalSpaceSmall,
|
||||
_buildTitle(),
|
||||
verticalSpaceSmall,
|
||||
_buildSubTitle()
|
||||
_buildSubtitle()
|
||||
];
|
||||
|
||||
Widget _buildIcon() => Icon(
|
||||
|
|
@ -54,15 +54,11 @@ class ProfileCard extends StatelessWidget {
|
|||
|
||||
Widget _buildTitle() => Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
color: kcDarkGrey,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: style16DG600,
|
||||
);
|
||||
|
||||
Widget _buildSubTitle() => Text(
|
||||
subTitle,
|
||||
style: const TextStyle(color: kcMediumGrey),
|
||||
Widget _buildSubtitle() => Text(
|
||||
subtitle,
|
||||
style: style14MG400,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1016,6 +1016,33 @@ class MockApiService extends _i1.Mock implements _i12.ApiService {
|
|||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i8.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i8.Future<Map<String, dynamic>> requestResetCode(
|
||||
Map<String, dynamic>? data) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#requestResetCode,
|
||||
[data],
|
||||
),
|
||||
returnValue:
|
||||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValueForMissingStub:
|
||||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i8.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i8.Future<Map<String, dynamic>> resetPassword(Map<String, dynamic>? data) =>
|
||||
(super.noSuchMethod(
|
||||
Invocation.method(
|
||||
#resetPassword,
|
||||
[data],
|
||||
),
|
||||
returnValue:
|
||||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
returnValueForMissingStub:
|
||||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||
) as _i8.Future<Map<String, dynamic>>);
|
||||
|
||||
@override
|
||||
_i8.Future<Map<String, dynamic>> getProfileStatus(_i11.UserModel? user) =>
|
||||
(super.noSuchMethod(
|
||||
|
|
|
|||
11
test/viewmodels/forget_password_viewmodel_test.dart
Normal file
11
test/viewmodels/forget_password_viewmodel_test.dart
Normal 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('ForgetPasswordViewModel Tests -', () {
|
||||
setUp(() => registerServices());
|
||||
tearDown(() => locator.reset());
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user