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/image_picker_service.dart';
|
||||||
import 'package:yimaru_app/services/google_auth_service.dart';
|
import 'package:yimaru_app/services/google_auth_service.dart';
|
||||||
import 'package:yimaru_app/services/image_downloader_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
|
// @stacked-import
|
||||||
|
|
||||||
@StackedApp(
|
@StackedApp(
|
||||||
|
|
@ -63,6 +64,7 @@ import 'package:yimaru_app/services/image_downloader_service.dart';
|
||||||
MaterialRoute(page: AssessmentView),
|
MaterialRoute(page: AssessmentView),
|
||||||
MaterialRoute(page: LearnLessonView),
|
MaterialRoute(page: LearnLessonView),
|
||||||
MaterialRoute(page: FailureView),
|
MaterialRoute(page: FailureView),
|
||||||
|
MaterialRoute(page: ForgetPasswordView),
|
||||||
// @stacked-route
|
// @stacked-route
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
// ignore_for_file: no_leading_underscores_for_library_prefixes
|
// 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:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart' as _i1;
|
import 'package:stacked/stacked.dart' as _i1;
|
||||||
import 'package:stacked_services/stacked_services.dart' as _i27;
|
import 'package:stacked_services/stacked_services.dart' as _i28;
|
||||||
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart'
|
||||||
as _i10;
|
as _i10;
|
||||||
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23;
|
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart' as _i23;
|
||||||
|
|
@ -16,6 +16,8 @@ import 'package:yimaru_app/ui/views/call_support/call_support_view.dart'
|
||||||
as _i13;
|
as _i13;
|
||||||
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
|
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart' as _i7;
|
||||||
import 'package:yimaru_app/ui/views/failure/failure_view.dart' as _i25;
|
import 'package:yimaru_app/ui/views/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/home/home_view.dart' as _i2;
|
||||||
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i14;
|
import 'package:yimaru_app/ui/views/language/language_view.dart' as _i14;
|
||||||
import 'package:yimaru_app/ui/views/learn/learn_view.dart' as _i19;
|
import 'package:yimaru_app/ui/views/learn/learn_view.dart' as _i19;
|
||||||
|
|
@ -92,6 +94,8 @@ class Routes {
|
||||||
|
|
||||||
static const failureView = '/failure-view';
|
static const failureView = '/failure-view';
|
||||||
|
|
||||||
|
static const forgetPasswordView = '/forget-password-view';
|
||||||
|
|
||||||
static const all = <String>{
|
static const all = <String>{
|
||||||
homeView,
|
homeView,
|
||||||
onboardingView,
|
onboardingView,
|
||||||
|
|
@ -117,6 +121,7 @@ class Routes {
|
||||||
assessmentView,
|
assessmentView,
|
||||||
learnLessonView,
|
learnLessonView,
|
||||||
failureView,
|
failureView,
|
||||||
|
forgetPasswordView,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,17 +223,21 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
Routes.failureView,
|
Routes.failureView,
|
||||||
page: _i25.FailureView,
|
page: _i25.FailureView,
|
||||||
),
|
),
|
||||||
|
_i1.RouteDef(
|
||||||
|
Routes.forgetPasswordView,
|
||||||
|
page: _i26.ForgetPasswordView,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
final _pagesMap = <Type, _i1.StackedRouteFactory>{
|
||||||
_i2.HomeView: (data) {
|
_i2.HomeView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i2.HomeView(),
|
builder: (context) => const _i2.HomeView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i3.OnboardingView: (data) {
|
_i3.OnboardingView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i3.OnboardingView(),
|
builder: (context) => const _i3.OnboardingView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
|
|
@ -237,141 +246,147 @@ class StackedRouter extends _i1.RouterBase {
|
||||||
final args = data.getArgs<StartupViewArguments>(
|
final args = data.getArgs<StartupViewArguments>(
|
||||||
orElse: () => const StartupViewArguments(),
|
orElse: () => const StartupViewArguments(),
|
||||||
);
|
);
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
builder: (context) => _i4.StartupView(key: args.key, label: args.label),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i5.ProfileView: (data) {
|
_i5.ProfileView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i5.ProfileView(),
|
builder: (context) => const _i5.ProfileView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i6.ProfileDetailView: (data) {
|
_i6.ProfileDetailView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i6.ProfileDetailView(),
|
builder: (context) => const _i6.ProfileDetailView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i7.DownloadsView: (data) {
|
_i7.DownloadsView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i7.DownloadsView(),
|
builder: (context) => const _i7.DownloadsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i8.ProgressView: (data) {
|
_i8.ProgressView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i8.ProgressView(),
|
builder: (context) => const _i8.ProgressView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i9.OngoingProgressView: (data) {
|
_i9.OngoingProgressView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i9.OngoingProgressView(),
|
builder: (context) => const _i9.OngoingProgressView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i10.AccountPrivacyView: (data) {
|
_i10.AccountPrivacyView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i10.AccountPrivacyView(),
|
builder: (context) => const _i10.AccountPrivacyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i11.SupportView: (data) {
|
_i11.SupportView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i11.SupportView(),
|
builder: (context) => const _i11.SupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i12.TelegramSupportView: (data) {
|
_i12.TelegramSupportView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i12.TelegramSupportView(),
|
builder: (context) => const _i12.TelegramSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i13.CallSupportView: (data) {
|
_i13.CallSupportView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i13.CallSupportView(),
|
builder: (context) => const _i13.CallSupportView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i14.LanguageView: (data) {
|
_i14.LanguageView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i14.LanguageView(),
|
builder: (context) => const _i14.LanguageView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i15.PrivacyPolicyView: (data) {
|
_i15.PrivacyPolicyView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i15.PrivacyPolicyView(),
|
builder: (context) => const _i15.PrivacyPolicyView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i16.TermsAndConditionsView: (data) {
|
_i16.TermsAndConditionsView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i16.TermsAndConditionsView(),
|
builder: (context) => const _i16.TermsAndConditionsView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i17.RegisterView: (data) {
|
_i17.RegisterView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i17.RegisterView(),
|
builder: (context) => const _i17.RegisterView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i18.LoginView: (data) {
|
_i18.LoginView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i18.LoginView(),
|
builder: (context) => const _i18.LoginView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i19.LearnView: (data) {
|
_i19.LearnView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i19.LearnView(),
|
builder: (context) => const _i19.LearnView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i20.LearnLevelView: (data) {
|
_i20.LearnLevelView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i20.LearnLevelView(),
|
builder: (context) => const _i20.LearnLevelView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i21.LearnModuleView: (data) {
|
_i21.LearnModuleView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i21.LearnModuleView(),
|
builder: (context) => const _i21.LearnModuleView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i22.WelcomeView: (data) {
|
_i22.WelcomeView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i22.WelcomeView(),
|
builder: (context) => const _i22.WelcomeView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i23.AssessmentView: (data) {
|
_i23.AssessmentView: (data) {
|
||||||
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
final args = data.getArgs<AssessmentViewArguments>(nullOk: false);
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i23.AssessmentView(key: args.key, data: args.data),
|
_i23.AssessmentView(key: args.key, data: args.data),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i24.LearnLessonView: (data) {
|
_i24.LearnLessonView: (data) {
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) => const _i24.LearnLessonView(),
|
builder: (context) => const _i24.LearnLessonView(),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
_i25.FailureView: (data) {
|
_i25.FailureView: (data) {
|
||||||
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
final args = data.getArgs<FailureViewArguments>(nullOk: false);
|
||||||
return _i26.MaterialPageRoute<dynamic>(
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
builder: (context) =>
|
builder: (context) =>
|
||||||
_i25.FailureView(key: args.key, label: args.label),
|
_i25.FailureView(key: args.key, label: args.label),
|
||||||
settings: data,
|
settings: data,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
_i26.ForgetPasswordView: (data) {
|
||||||
|
return _i27.MaterialPageRoute<dynamic>(
|
||||||
|
builder: (context) => const _i26.ForgetPasswordView(),
|
||||||
|
settings: data,
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -387,7 +402,7 @@ class StartupViewArguments {
|
||||||
this.label = 'Loading',
|
this.label = 'Loading',
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i26.Key? key;
|
final _i27.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -414,7 +429,7 @@ class AssessmentViewArguments {
|
||||||
required this.data,
|
required this.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i26.Key? key;
|
final _i27.Key? key;
|
||||||
|
|
||||||
final Map<String, dynamic> data;
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
|
@ -441,7 +456,7 @@ class FailureViewArguments {
|
||||||
required this.label,
|
required this.label,
|
||||||
});
|
});
|
||||||
|
|
||||||
final _i26.Key? key;
|
final _i27.Key? key;
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
|
|
||||||
|
|
@ -462,7 +477,7 @@ class FailureViewArguments {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NavigatorStateExtension on _i27.NavigationService {
|
extension NavigatorStateExtension on _i28.NavigationService {
|
||||||
Future<dynamic> navigateToHomeView([
|
Future<dynamic> navigateToHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -492,7 +507,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToStartupView({
|
Future<dynamic> navigateToStartupView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -761,7 +776,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToAssessmentView({
|
Future<dynamic> navigateToAssessmentView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -792,7 +807,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateToFailureView({
|
Future<dynamic> navigateToFailureView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -808,6 +823,20 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
transition: transition);
|
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([
|
Future<dynamic> replaceWithHomeView([
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -837,7 +866,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithStartupView({
|
Future<dynamic> replaceWithStartupView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
String label = 'Loading',
|
String label = 'Loading',
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1106,7 +1135,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithAssessmentView({
|
Future<dynamic> replaceWithAssessmentView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1137,7 +1166,7 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> replaceWithFailureView({
|
Future<dynamic> replaceWithFailureView({
|
||||||
_i26.Key? key,
|
_i27.Key? key,
|
||||||
required String label,
|
required String label,
|
||||||
int? routerId,
|
int? routerId,
|
||||||
bool preventDuplicates = true,
|
bool preventDuplicates = true,
|
||||||
|
|
@ -1152,4 +1181,18 @@ extension NavigatorStateExtension on _i27.NavigationService {
|
||||||
parameters: parameters,
|
parameters: parameters,
|
||||||
transition: transition);
|
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')
|
@JsonKey(name: 'profile_picture_url')
|
||||||
final String? profilePicture;
|
final String? profilePicture;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const UserModel({
|
const UserModel({
|
||||||
this.email,
|
this.email,
|
||||||
this.region,
|
this.region,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
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,
|
'user_id': instance.userId,
|
||||||
'last_name': instance.lastName,
|
'last_name': instance.lastName,
|
||||||
'birth_day': instance.birthday,
|
'birth_day': instance.birthday,
|
||||||
|
|
@ -31,9 +36,4 @@ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
||||||
'refresh_token': instance.refreshToken,
|
'refresh_token': instance.refreshToken,
|
||||||
'profile_completed': instance.profileCompleted,
|
'profile_completed': instance.profileCompleted,
|
||||||
'profile_picture_url': instance.profilePicture,
|
'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'
|
'message': 'Unknown Error Occurred'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login
|
// Email Login
|
||||||
Future<Map<String, dynamic>> emailLogin(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> emailLogin(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
|
|
@ -57,10 +57,10 @@ class ApiService {
|
||||||
'message': '${response.data['message']}, ${response.data['error']}'
|
'message': '${response.data['message']}, ${response.data['error']}'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,10 +85,10 @@ class ApiService {
|
||||||
'message': '${response.data['message']}, ${response.data['error']}'
|
'message': '${response.data['message']}, ${response.data['error']}'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -112,10 +112,10 @@ class ApiService {
|
||||||
'message': '${response.data['message']}, ${response.data['error']}'
|
'message': '${response.data['message']}, ${response.data['error']}'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,10 +139,65 @@ class ApiService {
|
||||||
'message': 'Unknown Error Occurred'
|
'message': 'Unknown Error Occurred'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'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']}'
|
'message': '${response.data['message']}, ${response.data['error']}'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -193,10 +248,10 @@ class ApiService {
|
||||||
'message': 'Unknown Error Occurred'
|
'message': 'Unknown Error Occurred'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -221,10 +276,10 @@ class ApiService {
|
||||||
'message': 'Unknown Error Occurred'
|
'message': 'Unknown Error Occurred'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'status': ResponseStatus.failure,
|
||||||
|
'message': e.response?.data.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -273,105 +328,14 @@ class ApiService {
|
||||||
'message': 'Unknown Error Occurred'
|
'message': 'Unknown Error Occurred'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} on DioException catch (e) {
|
||||||
return {
|
return {
|
||||||
'message': e.toString(),
|
|
||||||
'status': ResponseStatus.failure,
|
'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
|
// Assessments
|
||||||
Future<List<Assessment>> getAssessments() async {
|
Future<List<Assessment>> getAssessments() async {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,20 @@ class ImageDownloaderService {
|
||||||
final _service = locator<DioService>();
|
final _service = locator<DioService>();
|
||||||
|
|
||||||
Future<String> downloader(String? networkImage) async {
|
Future<String> downloader(String? networkImage) async {
|
||||||
final Directory appDir = await getApplicationDocumentsDirectory();
|
|
||||||
|
|
||||||
late File image;
|
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(
|
final Response profileImageResponse = await _service.dio.get(
|
||||||
'$kBaseUrl$networkImage',
|
profileImage,
|
||||||
options: Options(
|
options: Options(
|
||||||
followRedirects: false,
|
followRedirects: false,
|
||||||
responseType: ResponseType.bytes,
|
responseType: ResponseType.bytes,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ String kVerifyOtpUrl = 'verify-otp';
|
||||||
|
|
||||||
String kResendOtpUrl = 'resend-otp';
|
String kResendOtpUrl = 'resend-otp';
|
||||||
|
|
||||||
|
String kResetPassword = 'resetPassword';
|
||||||
|
|
||||||
|
String kRequestResetCode = 'sendResetCode';
|
||||||
|
|
||||||
String kUpdateProfileImage = 'profile-picture';
|
String kUpdateProfileImage = 'profile-picture';
|
||||||
|
|
||||||
String kRefreshTokenUrl = 'api/v1/auth/refresh';
|
String kRefreshTokenUrl = 'api/v1/auth/refresh';
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,15 @@ enum ProgressStatuses { pending, started, completed }
|
||||||
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
||||||
|
|
||||||
// State object
|
// 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,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
TextStyle style18DG500 = const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
color: kcDarkGrey,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
);
|
||||||
|
|
||||||
TextStyle style18DG600 = const TextStyle(
|
TextStyle style18DG600 = const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: kcDarkGrey,
|
color: kcDarkGrey,
|
||||||
|
|
@ -234,6 +240,8 @@ TextStyle style14LG400 = const TextStyle(
|
||||||
TextStyle style14MG400 = const TextStyle(
|
TextStyle style14MG400 = const TextStyle(
|
||||||
color: kcMediumGrey,
|
color: kcMediumGrey,
|
||||||
);
|
);
|
||||||
|
TextStyle style14DG500 =
|
||||||
|
const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500);
|
||||||
|
|
||||||
TextStyle style14DG400 = const TextStyle(
|
TextStyle style14DG400 = const TextStyle(
|
||||||
color: kcDarkGrey,
|
color: kcDarkGrey,
|
||||||
|
|
@ -274,24 +282,27 @@ Map<String, Style> htmlStyle = {
|
||||||
Widget buildToastDescription(String message) => Text(
|
Widget buildToastDescription(String message) => Text(
|
||||||
message,
|
message,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
style: const TextStyle(color: kcWhite, fontWeight: FontWeight.w500),
|
style: const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500),
|
||||||
);
|
);
|
||||||
|
|
||||||
void showErrorToast(String message) {
|
void showErrorToast(String message) {
|
||||||
toastification.show(
|
toastification.show(
|
||||||
showIcon: true,
|
showIcon: true,
|
||||||
dragToClose: true,
|
dragToClose: true,
|
||||||
primaryColor: kcRed,
|
|
||||||
showProgressBar: false,
|
showProgressBar: false,
|
||||||
applyBlurEffect: false,
|
applyBlurEffect: false,
|
||||||
icon: const Icon(Icons.check),
|
alignment: Alignment.topCenter,
|
||||||
|
primaryColor: kcBackgroundColor,
|
||||||
type: ToastificationType.success,
|
type: ToastificationType.success,
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
style: ToastificationStyle.fillColored,
|
style: ToastificationStyle.fillColored,
|
||||||
description: buildToastDescription(message),
|
description: buildToastDescription(message),
|
||||||
borderSide: const BorderSide(color: kcWhite),
|
autoCloseDuration: const Duration(seconds: 3),
|
||||||
autoCloseDuration: const Duration(seconds: 5),
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
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,
|
dragToClose: true,
|
||||||
showProgressBar: false,
|
showProgressBar: false,
|
||||||
applyBlurEffect: false,
|
applyBlurEffect: false,
|
||||||
icon: const Icon(Icons.check),
|
alignment: Alignment.topCenter,
|
||||||
primaryColor: kcPrimaryColor,
|
primaryColor: kcBackgroundColor,
|
||||||
type: ToastificationType.success,
|
type: ToastificationType.success,
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
style: ToastificationStyle.fillColored,
|
style: ToastificationStyle.fillColored,
|
||||||
description: buildToastDescription(message),
|
description: buildToastDescription(message),
|
||||||
borderSide: const BorderSide(color: kcWhite),
|
autoCloseDuration: const Duration(seconds: 3),
|
||||||
autoCloseDuration: const Duration(seconds: 5),
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
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(
|
Widget _buildHeader(String title) => Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: style18DG600,
|
||||||
fontSize: 18,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
|
// In-app navigation
|
||||||
int _currentPage = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
int get currentPage => _currentPage;
|
int get currentPage => _currentPage;
|
||||||
|
|
@ -255,16 +256,17 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
|
|
||||||
// Complete profile
|
// Complete profile
|
||||||
Future<void> completeProfile() async =>
|
Future<void> completeProfile() async =>
|
||||||
await runBusyFuture(_completeProfile());
|
await runBusyFuture(_completeProfile(),
|
||||||
|
busyObject: StateObjects.profileCompletion);
|
||||||
|
|
||||||
Future<void> _completeProfile() async {
|
Future<void> _completeProfile() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
Map<String, dynamic> response =
|
Map<String, dynamic> response =
|
||||||
await _apiService.completeProfile(_userData);
|
await _apiService.completeProfile(_userData);
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
showSuccessToast(response['message']);
|
|
||||||
clearUserData();
|
clearUserData();
|
||||||
await replaceWithHome();
|
await replaceWithHome();
|
||||||
|
showSuccessToast(response['message']);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(response['message']);
|
showErrorToast(response['message']);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,27 +61,23 @@ class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildIcon() => SvgPicture.asset(
|
Widget _buildIcon() => SvgPicture.asset(
|
||||||
'assets/icons/complete.svg',
|
'assets/icons/complete.svg',
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Assessment complete!',
|
'Assessment complete!',
|
||||||
|
style: style25DG600,
|
||||||
textAlign: TextAlign.center,
|
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',
|
'We’re now analyzing your speaking skills',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
||||||
|
|
@ -94,8 +90,8 @@ class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
height: 55,
|
height: 55,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
text: 'View My Results',
|
text: 'View My Results',
|
||||||
onTap: () => viewModel.next(),
|
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
|
onTap: () => viewModel.next(),
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,25 +64,21 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildIcon() => SvgPicture.asset('assets/icons/alert.svg');
|
Widget _buildIcon() => SvgPicture.asset('assets/icons/alert.svg');
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'We didn’t get enough from your assessment',
|
'We didn’t get enough from your assessment',
|
||||||
|
style: style25DG600,
|
||||||
textAlign: TextAlign.center,
|
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 ',
|
'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,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||||
|
|
@ -117,9 +113,9 @@ class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
height: 55,
|
height: 55,
|
||||||
text: 'Skip',
|
text: 'Skip',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
backgroundColor: kcWhite,
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
onTap: () => viewModel.next(),
|
onTap: () => viewModel.next(),
|
||||||
backgroundColor: kcWhite,
|
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||||
|
|
@ -69,7 +69,7 @@ class AssessmentIntroScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
style: style25DG600,
|
style: style25DG600,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Answer a few quick questions to help us understand your English proficiency.',
|
'Answer a few quick questions to help us understand your English proficiency.',
|
||||||
style: style14MG400,
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,5 @@ class AssessmentLoadingScreen extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildPageIndicator() => const PageLoadingIndicator();
|
Widget _buildPageIndicator() => const PageLoadingIndicator();
|
||||||
|
|
||||||
Widget _buildRefreshButton() => RefreshButton(
|
Widget _buildRefreshButton() => RefreshButton(onTap: onTap);
|
||||||
onTap: onTap,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,11 +63,11 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceLarge,
|
verticalSpaceLarge,
|
||||||
_buildTitle(viewModel),
|
_buildTitle(viewModel),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildPrimarySubTitle(),
|
_buildPrimarySubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildIconWrapper(viewModel),
|
_buildIconWrapper(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildSecondarySubTitle()
|
_buildSecondarySubtitle()
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
|
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
|
||||||
|
|
@ -76,10 +76,10 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPrimarySubTitle() => const Text(
|
Widget _buildPrimarySubtitle() => Text(
|
||||||
'Great Job! Here’s your next step to keep improving.',
|
'Great Job! Here’s your next step to keep improving.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
|
Widget _buildIconWrapper(AssessmentViewModel viewModel) =>
|
||||||
|
|
@ -90,7 +90,7 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
Widget _buildIcon(AssessmentViewModel viewModel) => SvgPicture.asset(
|
Widget _buildIcon(AssessmentViewModel viewModel) => SvgPicture.asset(
|
||||||
'assets/icons/${viewModel.proficiencyLevel.name.substring(0, 1)}_${viewModel.proficiencyLevel.name.substring(1)}.svg');
|
'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',
|
'Let\'s start your practice',
|
||||||
style: style14DG400,
|
style: style14DG400,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
|
@ -113,8 +113,8 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
safe: false,
|
safe: false,
|
||||||
text: 'Continue',
|
text: 'Continue',
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
onTap: () => viewModel.next(),
|
|
||||||
foregroundColor: kcWhite,
|
foregroundColor: kcWhite,
|
||||||
|
onTap: () => viewModel.next(),
|
||||||
backgroundColor: kcPrimaryColor,
|
backgroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -127,10 +127,10 @@ class AssessmentResultScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
CustomElevatedButton(
|
CustomElevatedButton(
|
||||||
height: 55,
|
height: 55,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
|
backgroundColor: kcWhite,
|
||||||
text: 'Practice Speaking',
|
text: 'Practice Speaking',
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
onTap: () => viewModel.next(),
|
onTap: () => viewModel.next(),
|
||||||
backgroundColor: kcWhite,
|
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ class ResultAnalysisScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||||
|
|
@ -61,19 +61,15 @@ class ResultAnalysisScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
'assets/icons/progress_indicator.svg',
|
'assets/icons/progress_indicator.svg',
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Analyzing your results…',
|
'Analyzing your results…',
|
||||||
|
style: style25DG600,
|
||||||
textAlign: TextAlign.center,
|
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',
|
'We’re now analyzing your speaking skills',
|
||||||
|
style: style14MG400,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||||
|
|
@ -72,20 +72,16 @@ class RetakeAssessmentScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
color: kcPrimaryColor,
|
color: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'We didn’t get enough from your assessment',
|
'We didn’t get enough from your assessment',
|
||||||
|
style: style25DG600,
|
||||||
textAlign: TextAlign.center,
|
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 ',
|
'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,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||||
|
|
|
||||||
|
|
@ -80,21 +80,25 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(viewModel),
|
_buildTitle(viewModel),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg');
|
Widget _buildIcon() => SvgPicture.asset('assets/icons/mascot.svg');
|
||||||
|
|
||||||
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
|
Widget _buildTitle(AssessmentViewModel viewModel) => Text.rich(
|
||||||
TextSpan(text: 'Welcome aboard', style: style25DG600, children: [
|
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: ', ${viewModel.userData['first_name']}!',
|
text: 'Welcome aboard',
|
||||||
style: style25DG600,
|
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.',
|
'You’re ready to explore your personalized lessons.',
|
||||||
style: style14MG400,
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
@ -115,5 +119,7 @@ class StartLessonScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildState(AssessmentViewModel viewModel) =>
|
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() =>
|
Widget _buildIcon() =>
|
||||||
const CircularIcon(icon: Icons.call, size: 50, color: kcPrimaryColor);
|
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',
|
'Call our support team between 9 AM - 6 PM',
|
||||||
|
style: style25DG600,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle(String title) => Text(
|
Widget _buildSubTitle(String title) => Text(
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildEmptyTitle(),
|
_buildEmptyTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildEmptySubTitle(),
|
_buildEmptySubtitle(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildEmptyIcon() => const Icon(
|
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.',
|
'Start by exploring your learning materials and save them for offline access.',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(color: kcMediumGrey),
|
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};
|
response = {'data': true, 'status': ResponseStatus.success};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (response['status'] == ResponseStatus.success && !response['data']) {
|
if (response['status'] == ResponseStatus.success && !response['data']) {
|
||||||
await replaceWithOnboarding();
|
await replaceWithOnboarding();
|
||||||
} else if (response['status'] == ResponseStatus.success &&
|
} else if (response['status'] == ResponseStatus.success &&
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildLanguages(viewModel)
|
_buildLanguages(viewModel)
|
||||||
];
|
];
|
||||||
|
|
@ -72,22 +72,18 @@ class LanguageView extends StackedView<LanguageViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
|
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
|
||||||
title: 'Language Preference',
|
|
||||||
onTap: viewModel.pop,
|
onTap: viewModel.pop,
|
||||||
|
title: 'Language Preference',
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Choose your language',
|
'Choose your language',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'You can switch languages anytime',
|
'You can switch languages anytime',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLanguages(LanguageViewModel viewModel) => ListView.builder(
|
Widget _buildLanguages(LanguageViewModel viewModel) => ListView.builder(
|
||||||
|
|
|
||||||
|
|
@ -73,20 +73,14 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
|
||||||
_buildListView(viewModel)
|
_buildListView(viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'A1 - Beginner',
|
'A1 - Beginner',
|
||||||
style: TextStyle(
|
style: style18P600,
|
||||||
fontSize: 18,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubTitle() => Text(
|
||||||
'Your Current Level',
|
'Your Current Level',
|
||||||
style: TextStyle(
|
style: style14DG400,
|
||||||
color: kcDarkGrey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildOverallProgress() => const OverallLearnProgress();
|
Widget _buildOverallProgress() => const OverallLearnProgress();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked/stacked_annotations.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_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_email_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/login/screens/login_with_phone_number_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
|
@override
|
||||||
void onViewModelReady(LoginViewModel viewModel) {
|
void onViewModelReady(LoginViewModel viewModel) {
|
||||||
|
_clearData();
|
||||||
syncFormWithViewModel(viewModel);
|
syncFormWithViewModel(viewModel);
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _clearData() {
|
||||||
|
otpController.clear();
|
||||||
|
emailController.clear();
|
||||||
|
passwordController.clear();
|
||||||
|
phoneNumberController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LoginViewModel viewModelBuilder(BuildContext context) => LoginViewModel();
|
LoginViewModel viewModelBuilder(BuildContext context) => LoginViewModel();
|
||||||
|
|
||||||
|
|
@ -52,8 +61,11 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
||||||
body: _buildScaffoldStack(viewModel),
|
body: _buildScaffoldStack(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildScaffoldStack(LoginViewModel viewModel) =>
|
Widget _buildScaffoldStack(LoginViewModel viewModel) => Stack(children: [
|
||||||
Stack(children: [_buildScaffold(viewModel), _buildBusyLogin(viewModel)]);
|
_buildScaffold(viewModel),
|
||||||
|
_buildLoginWithEmailState(viewModel),
|
||||||
|
_buildLoginWithGoogleState(viewModel)
|
||||||
|
]);
|
||||||
|
|
||||||
Widget _buildScaffold(LoginViewModel viewModel) => Column(
|
Widget _buildScaffold(LoginViewModel viewModel) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -94,6 +106,13 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
|
||||||
otpController: otpController,
|
otpController: otpController,
|
||||||
phoneNumberController: phoneNumberController);
|
phoneNumberController: phoneNumberController);
|
||||||
|
|
||||||
Widget _buildBusyLogin(LoginViewModel viewModel) =>
|
Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
|
||||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
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 =>
|
Future<void> navigateToRegister() async =>
|
||||||
await _navigationService.navigateToRegisterView();
|
await _navigationService.navigateToRegisterView();
|
||||||
|
|
||||||
|
Future<void> navigateToForgetPassword() async =>
|
||||||
|
await _navigationService.navigateToForgetPasswordView();
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
Future<void> replaceWithHome() async =>
|
||||||
await _navigationService.clearStackAndShowView(const HomeView());
|
await _navigationService.clearStackAndShowView(const HomeView());
|
||||||
|
|
||||||
// Remote api calls
|
// Remote api calls
|
||||||
|
|
||||||
// Login with email
|
// Login with email
|
||||||
Future<void> emailLogin() async => await runBusyFuture(_emailLogin());
|
Future<void> emailLogin() async => await runBusyFuture(_emailLogin(),
|
||||||
|
busyObject: StateObjects.loginWithEmail);
|
||||||
|
|
||||||
Future<void> _emailLogin() async {
|
Future<void> _emailLogin() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
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 {
|
Future<void> _googleLogin() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
_buildSubTitleWrapper(viewModel),
|
_buildSubtitleWrapper(viewModel),
|
||||||
verticalSpaceLarge,
|
verticalSpaceLarge,
|
||||||
_buildEmailFormField(viewModel),
|
_buildEmailFormField(viewModel),
|
||||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
||||||
|
|
@ -71,19 +71,15 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
verticalSpaceTiny,
|
verticalSpaceTiny,
|
||||||
if (viewModel.hasPasswordValidationMessage && viewModel.focusPassword)
|
if (viewModel.hasPasswordValidationMessage && viewModel.focusPassword)
|
||||||
_buildPasswordValidationWrapper(viewModel),
|
_buildPasswordValidationWrapper(viewModel),
|
||||||
_buildForgetPasswordTextButtonWrapper(),
|
_buildForgetPasswordTextButtonWrapper(viewModel),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Welcome Back',
|
'Welcome Back',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
Widget _buildSubtitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||||
onTap: () async => await viewModel.navigateToRegister(),
|
onTap: () async => await viewModel.navigateToRegister(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -104,11 +100,7 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
|
|
||||||
Widget _buildEmailValidator(LoginViewModel viewModel) => Text(
|
Widget _buildEmailValidator(LoginViewModel viewModel) => Text(
|
||||||
viewModel.emailValidationMessage!,
|
viewModel.emailValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPasswordFormField(LoginViewModel viewModel) => TextFormField(
|
Widget _buildPasswordFormField(LoginViewModel viewModel) => TextFormField(
|
||||||
|
|
@ -135,26 +127,23 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
|
|
||||||
Widget _buildPasswordValidator(LoginViewModel viewModel) => Text(
|
Widget _buildPasswordValidator(LoginViewModel viewModel) => Text(
|
||||||
viewModel.passwordValidationMessage!,
|
viewModel.passwordValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildForgetPasswordTextButtonWrapper() => Align(
|
Widget _buildForgetPasswordTextButtonWrapper(LoginViewModel viewModel) =>
|
||||||
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: _buildForgetPasswordTextButton(),
|
child: _buildForgetPasswordTextButton(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildForgetPasswordTextButton() => TextButton(
|
Widget _buildForgetPasswordTextButton(LoginViewModel viewModel) => TextButton(
|
||||||
onPressed: () {},
|
onPressed: () async => await viewModel.navigateToForgetPassword(),
|
||||||
child: _buildForgetPasswordText(),
|
child: _buildForgetPasswordText(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildForgetPasswordText() => const Text(
|
Widget _buildForgetPasswordText() => Text(
|
||||||
'Forget Password?',
|
'Forget Password?',
|
||||||
style: TextStyle(color: kcPrimaryColor),
|
style: style14P400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLowerColumn(LoginViewModel viewModel) => Column(
|
Widget _buildLowerColumn(LoginViewModel viewModel) => Column(
|
||||||
|
|
@ -207,8 +196,8 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
backgroundColor: kcWhite,
|
backgroundColor: kcWhite,
|
||||||
leadingIcon: Icons.phone,
|
leadingIcon: Icons.phone,
|
||||||
borderColor: kcPrimaryColor,
|
borderColor: kcPrimaryColor,
|
||||||
|
onTap: () => viewModel.goTo(1),
|
||||||
foregroundColor: kcPrimaryColor,
|
foregroundColor: kcPrimaryColor,
|
||||||
text: 'Login with Phone Number',
|
text: 'Login with Phone Number',
|
||||||
onTap: () => viewModel.goTo(1),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
_buildSubTitleWrapper(viewModel),
|
_buildSubtitleWrapper(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildSubtitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
|
|
@ -57,22 +57,18 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
|
||||||
_buildPhoneNumberValidatorWrapper(viewModel),
|
_buildPhoneNumberValidatorWrapper(viewModel),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Welcome Back',
|
'Welcome Back',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
Widget _buildSubtitleWrapper(LoginViewModel viewModel) => RegisterForAccount(
|
||||||
onTap: () async => await viewModel.navigateToRegister(),
|
onTap: () async => await viewModel.navigateToRegister(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubtitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Enter your phone number. We will send you a confirmation code there',
|
'Enter your phone number. We will send you a confirmation code there',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPhoneNumberWrapper(LoginViewModel viewModel) => Row(
|
Widget _buildPhoneNumberWrapper(LoginViewModel viewModel) => Row(
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ import 'onboarding_viewmodel.dart';
|
||||||
import 'onboarding_view.form.dart';
|
import 'onboarding_view.form.dart';
|
||||||
|
|
||||||
@FormView(fields: [
|
@FormView(fields: [
|
||||||
FormTextField(name: 'answer', validator: FormValidator.validateForm),
|
|
||||||
FormTextField(name: 'fullName', validator: FormValidator.validateForm),
|
FormTextField(name: 'fullName', validator: FormValidator.validateForm),
|
||||||
FormTextField(name: 'challenge', validator: FormValidator.validateForm),
|
FormTextField(name: 'challenge', validator: FormValidator.validateForm),
|
||||||
FormTextField(name: 'occupation', validator: FormValidator.validateForm),
|
FormTextField(name: 'occupation', validator: FormValidator.validateForm),
|
||||||
|
|
@ -30,13 +29,55 @@ class OnboardingView extends StackedView<OnboardingViewModel>
|
||||||
with $OnboardingView {
|
with $OnboardingView {
|
||||||
const OnboardingView({Key? key}) : super(key: key);
|
const OnboardingView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
void _initFormFields() {
|
void _initClearData() {
|
||||||
answerController.text = 'Book';
|
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
|
@override
|
||||||
void onViewModelReady(OnboardingViewModel viewModel) {
|
void onViewModelReady(OnboardingViewModel viewModel) {
|
||||||
_initFormFields();
|
_initClearData();
|
||||||
syncFormWithViewModel(viewModel);
|
syncFormWithViewModel(viewModel);
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +99,7 @@ class OnboardingView extends StackedView<OnboardingViewModel>
|
||||||
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
|
Widget _buildOnboardingScreensWrapper(OnboardingViewModel viewModel) =>
|
||||||
PopScope(
|
PopScope(
|
||||||
canPop: viewModel.currentPage == 0 ? true : false,
|
canPop: viewModel.currentPage == 0 ? true : false,
|
||||||
onPopInvokedWithResult: (value, data) => viewModel.pop(),
|
onPopInvokedWithResult: (value, data) => _pop(viewModel),
|
||||||
child: _buildOnboardingScreens(viewModel));
|
child: _buildOnboardingScreens(viewModel));
|
||||||
|
|
||||||
Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
|
Widget _buildOnboardingScreens(OnboardingViewModel viewModel) => IndexedStack(
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:yimaru_app/ui/common/validators/form_validator.dart';
|
||||||
|
|
||||||
const bool _autoTextFieldValidation = true;
|
const bool _autoTextFieldValidation = true;
|
||||||
|
|
||||||
const String AnswerValueKey = 'answer';
|
|
||||||
const String FullNameValueKey = 'fullName';
|
const String FullNameValueKey = 'fullName';
|
||||||
const String ChallengeValueKey = 'challenge';
|
const String ChallengeValueKey = 'challenge';
|
||||||
const String OccupationValueKey = 'occupation';
|
const String OccupationValueKey = 'occupation';
|
||||||
|
|
@ -25,7 +24,6 @@ final Map<String, TextEditingController> _OnboardingViewTextEditingControllers =
|
||||||
final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
|
final Map<String, FocusNode> _OnboardingViewFocusNodes = {};
|
||||||
|
|
||||||
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
|
final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
|
||||||
AnswerValueKey: FormValidator.validateForm,
|
|
||||||
FullNameValueKey: FormValidator.validateForm,
|
FullNameValueKey: FormValidator.validateForm,
|
||||||
ChallengeValueKey: FormValidator.validateForm,
|
ChallengeValueKey: FormValidator.validateForm,
|
||||||
OccupationValueKey: FormValidator.validateForm,
|
OccupationValueKey: FormValidator.validateForm,
|
||||||
|
|
@ -34,8 +32,6 @@ final Map<String, String? Function(String?)?> _OnboardingViewTextValidations = {
|
||||||
};
|
};
|
||||||
|
|
||||||
mixin $OnboardingView {
|
mixin $OnboardingView {
|
||||||
TextEditingController get answerController =>
|
|
||||||
_getFormTextEditingController(AnswerValueKey);
|
|
||||||
TextEditingController get fullNameController =>
|
TextEditingController get fullNameController =>
|
||||||
_getFormTextEditingController(FullNameValueKey);
|
_getFormTextEditingController(FullNameValueKey);
|
||||||
TextEditingController get challengeController =>
|
TextEditingController get challengeController =>
|
||||||
|
|
@ -47,7 +43,6 @@ mixin $OnboardingView {
|
||||||
TextEditingController get topicController =>
|
TextEditingController get topicController =>
|
||||||
_getFormTextEditingController(TopicValueKey);
|
_getFormTextEditingController(TopicValueKey);
|
||||||
|
|
||||||
FocusNode get answerFocusNode => _getFormFocusNode(AnswerValueKey);
|
|
||||||
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
|
FocusNode get fullNameFocusNode => _getFormFocusNode(FullNameValueKey);
|
||||||
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
|
FocusNode get challengeFocusNode => _getFormFocusNode(ChallengeValueKey);
|
||||||
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
|
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
|
||||||
|
|
@ -79,7 +74,6 @@ mixin $OnboardingView {
|
||||||
/// Registers a listener on every generated controller that calls [model.setData()]
|
/// Registers a listener on every generated controller that calls [model.setData()]
|
||||||
/// with the latest textController values
|
/// with the latest textController values
|
||||||
void syncFormWithViewModel(FormStateHelper model) {
|
void syncFormWithViewModel(FormStateHelper model) {
|
||||||
answerController.addListener(() => _updateFormData(model));
|
|
||||||
fullNameController.addListener(() => _updateFormData(model));
|
fullNameController.addListener(() => _updateFormData(model));
|
||||||
challengeController.addListener(() => _updateFormData(model));
|
challengeController.addListener(() => _updateFormData(model));
|
||||||
occupationController.addListener(() => _updateFormData(model));
|
occupationController.addListener(() => _updateFormData(model));
|
||||||
|
|
@ -96,7 +90,6 @@ mixin $OnboardingView {
|
||||||
'This feature was deprecated after 3.1.0.',
|
'This feature was deprecated after 3.1.0.',
|
||||||
)
|
)
|
||||||
void listenToFormUpdated(FormViewModel model) {
|
void listenToFormUpdated(FormViewModel model) {
|
||||||
answerController.addListener(() => _updateFormData(model));
|
|
||||||
fullNameController.addListener(() => _updateFormData(model));
|
fullNameController.addListener(() => _updateFormData(model));
|
||||||
challengeController.addListener(() => _updateFormData(model));
|
challengeController.addListener(() => _updateFormData(model));
|
||||||
occupationController.addListener(() => _updateFormData(model));
|
occupationController.addListener(() => _updateFormData(model));
|
||||||
|
|
@ -111,7 +104,6 @@ mixin $OnboardingView {
|
||||||
model.setData(
|
model.setData(
|
||||||
model.formValueMap
|
model.formValueMap
|
||||||
..addAll({
|
..addAll({
|
||||||
AnswerValueKey: answerController.text,
|
|
||||||
FullNameValueKey: fullNameController.text,
|
FullNameValueKey: fullNameController.text,
|
||||||
ChallengeValueKey: challengeController.text,
|
ChallengeValueKey: challengeController.text,
|
||||||
OccupationValueKey: occupationController.text,
|
OccupationValueKey: occupationController.text,
|
||||||
|
|
@ -158,7 +150,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
return !hasAnyValidationMessage;
|
return !hasAnyValidationMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get answerValue => this.formValueMap[AnswerValueKey] as String?;
|
|
||||||
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
|
String? get fullNameValue => this.formValueMap[FullNameValueKey] as String?;
|
||||||
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
|
String? get challengeValue => this.formValueMap[ChallengeValueKey] as String?;
|
||||||
String? get occupationValue =>
|
String? get occupationValue =>
|
||||||
|
|
@ -167,16 +158,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
this.formValueMap[LanguageGoalValueKey] as String?;
|
this.formValueMap[LanguageGoalValueKey] as String?;
|
||||||
String? get topicValue => this.formValueMap[TopicValueKey] 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) {
|
set fullNameValue(String? value) {
|
||||||
this.setData(
|
this.setData(
|
||||||
this.formValueMap..addAll({FullNameValueKey: value}),
|
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 =>
|
bool get hasFullName =>
|
||||||
this.formValueMap.containsKey(FullNameValueKey) &&
|
this.formValueMap.containsKey(FullNameValueKey) &&
|
||||||
(fullNameValue?.isNotEmpty ?? false);
|
(fullNameValue?.isNotEmpty ?? false);
|
||||||
|
|
@ -251,8 +229,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
this.formValueMap.containsKey(TopicValueKey) &&
|
this.formValueMap.containsKey(TopicValueKey) &&
|
||||||
(topicValue?.isNotEmpty ?? false);
|
(topicValue?.isNotEmpty ?? false);
|
||||||
|
|
||||||
bool get hasAnswerValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[AnswerValueKey]?.isNotEmpty ?? false;
|
|
||||||
bool get hasFullNameValidationMessage =>
|
bool get hasFullNameValidationMessage =>
|
||||||
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[FullNameValueKey]?.isNotEmpty ?? false;
|
||||||
bool get hasChallengeValidationMessage =>
|
bool get hasChallengeValidationMessage =>
|
||||||
|
|
@ -264,8 +240,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
bool get hasTopicValidationMessage =>
|
bool get hasTopicValidationMessage =>
|
||||||
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
|
this.fieldsValidationMessages[TopicValueKey]?.isNotEmpty ?? false;
|
||||||
|
|
||||||
String? get answerValidationMessage =>
|
|
||||||
this.fieldsValidationMessages[AnswerValueKey];
|
|
||||||
String? get fullNameValidationMessage =>
|
String? get fullNameValidationMessage =>
|
||||||
this.fieldsValidationMessages[FullNameValueKey];
|
this.fieldsValidationMessages[FullNameValueKey];
|
||||||
String? get challengeValidationMessage =>
|
String? get challengeValidationMessage =>
|
||||||
|
|
@ -279,8 +253,6 @@ extension ValueProperties on FormStateHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Methods on FormStateHelper {
|
extension Methods on FormStateHelper {
|
||||||
setAnswerValidationMessage(String? validationMessage) =>
|
|
||||||
this.fieldsValidationMessages[AnswerValueKey] = validationMessage;
|
|
||||||
setFullNameValidationMessage(String? validationMessage) =>
|
setFullNameValidationMessage(String? validationMessage) =>
|
||||||
this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
|
this.fieldsValidationMessages[FullNameValueKey] = validationMessage;
|
||||||
setChallengeValidationMessage(String? validationMessage) =>
|
setChallengeValidationMessage(String? validationMessage) =>
|
||||||
|
|
@ -294,7 +266,6 @@ extension Methods on FormStateHelper {
|
||||||
|
|
||||||
/// Clears text input fields on the Form
|
/// Clears text input fields on the Form
|
||||||
void clearForm() {
|
void clearForm() {
|
||||||
answerValue = '';
|
|
||||||
fullNameValue = '';
|
fullNameValue = '';
|
||||||
challengeValue = '';
|
challengeValue = '';
|
||||||
occupationValue = '';
|
occupationValue = '';
|
||||||
|
|
@ -305,7 +276,6 @@ extension Methods on FormStateHelper {
|
||||||
/// Validates text input fields on the Form
|
/// Validates text input fields on the Form
|
||||||
void validateForm() {
|
void validateForm() {
|
||||||
this.setValidationMessages({
|
this.setValidationMessages({
|
||||||
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
|
||||||
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
||||||
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
||||||
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
||||||
|
|
@ -330,7 +300,6 @@ String? getValidationMessage(String key) {
|
||||||
/// Updates the fieldsValidationMessages on the FormViewModel
|
/// Updates the fieldsValidationMessages on the FormViewModel
|
||||||
void updateValidationData(FormStateHelper model) =>
|
void updateValidationData(FormStateHelper model) =>
|
||||||
model.setValidationMessages({
|
model.setValidationMessages({
|
||||||
AnswerValueKey: getValidationMessage(AnswerValueKey),
|
|
||||||
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
FullNameValueKey: getValidationMessage(FullNameValueKey),
|
||||||
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
ChallengeValueKey: getValidationMessage(ChallengeValueKey),
|
||||||
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
OccupationValueKey: getValidationMessage(OccupationValueKey),
|
||||||
|
|
|
||||||
|
|
@ -372,16 +372,83 @@ class OnboardingViewModel extends FormViewModel {
|
||||||
_userData.clear();
|
_userData.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Form reset
|
||||||
Future<void> navigateToLanguage() async =>
|
|
||||||
await _navigationService.navigateToLanguageView();
|
|
||||||
|
|
||||||
Future<void> navigateToAssessment() async =>
|
// Reset full name form screen
|
||||||
await _navigationService.navigateToAssessmentView(data: _userData);
|
void resetFullNameFormScreen() {
|
||||||
|
_focusFullName = false;
|
||||||
|
rebuildUi();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
// Reset gender form screen
|
||||||
await _navigationService.clearStackAndShowView(const HomeView());
|
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 {
|
void next({int? page}) async {
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
if (_previousPage != 0) {
|
if (_previousPage != 0) {
|
||||||
|
|
@ -396,7 +463,7 @@ class OnboardingViewModel extends FormViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop() {
|
void goBack() {
|
||||||
if (_currentPage == 0) {
|
if (_currentPage == 0) {
|
||||||
_navigationService.back();
|
_navigationService.back();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -405,4 +472,14 @@ class OnboardingViewModel extends FormViewModel {
|
||||||
rebuildUi();
|
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> {
|
class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const AgeGroupFormScreen({super.key});
|
const AgeGroupFormScreen({super.key});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetAgeGroupFormScreen();
|
||||||
|
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -74,24 +80,20 @@ class AgeGroupFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Which age range are you in?',
|
'Which age range are you in?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubTitle() => Text(
|
||||||
'We’ll personalize your learning experience based on your age.',
|
'We’ll personalize your learning experience based on your age.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14DG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,12 @@ import '../../../widgets/birthday_selector.dart';
|
||||||
class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const BirthdayFormScreen({super.key});
|
const BirthdayFormScreen({super.key});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetBirthdayFormScreen();
|
||||||
|
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -64,30 +70,26 @@ class BirthdayFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildBirthdayFormField(viewModel)
|
_buildBirthdayFormField(viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Pick your birthday?',
|
'Pick your birthday?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'We’ll personalize your learning experience based on your birthday.',
|
'We’ll personalize your learning experience based on your birthday.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
|
Widget _buildBirthdayFormField(OnboardingViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
const ChallengeFormScreen({super.key, required this.challengeController});
|
const ChallengeFormScreen({super.key, required this.challengeController});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
challengeController.clear();
|
||||||
|
viewModel.resetChallengeFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -74,7 +80,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildChallenges(viewModel),
|
_buildChallenges(viewModel),
|
||||||
if (viewModel.showChallengeTextBox) _buildChallengeFormField(viewModel),
|
if (viewModel.showChallengeTextBox) _buildChallengeFormField(viewModel),
|
||||||
|
|
@ -90,24 +96,20 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'What challenge do you face most with English?',
|
'What challenge do you face most with English?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Everyone has struggles, let’s start fixing yours 😊',
|
'Everyone has struggles, let’s start fixing yours 😊',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildChallenges(OnboardingViewModel viewModel) => ListView.builder(
|
Widget _buildChallenges(OnboardingViewModel viewModel) => ListView.builder(
|
||||||
|
|
@ -151,11 +153,7 @@ class ChallengeFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildChallengeValidator(OnboardingViewModel viewModel) => Text(
|
Widget _buildChallengeValidator(OnboardingViewModel viewModel) => Text(
|
||||||
viewModel.challengeValidationMessage!,
|
viewModel.challengeValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const CountryRegionFormScreen({super.key});
|
const CountryRegionFormScreen({super.key});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetCountryRegionFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -71,7 +76,7 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildCountryDropDown(viewModel),
|
_buildCountryDropDown(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
|
|
@ -80,24 +85,20 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Where are you from?',
|
'Where are you from?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Select your country and region from the dropdown',
|
'Select your country and region from the dropdown',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
|
Widget _buildCountryDropDown(OnboardingViewModel viewModel) =>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ class EducationalBackgroundFormScreen
|
||||||
extends ViewModelWidget<OnboardingViewModel> {
|
extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const EducationalBackgroundFormScreen({super.key});
|
const EducationalBackgroundFormScreen({super.key});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetEducationalBackgroundFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -77,9 +82,9 @@ class EducationalBackgroundFormScreen
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class FullNameFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceLarge,
|
verticalSpaceLarge,
|
||||||
_buildFullNameFormField(viewModel),
|
_buildFullNameFormField(viewModel),
|
||||||
if (viewModel.hasFullNameValidationMessage && viewModel.focusFullName)
|
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.',
|
'We’ll use your name to personalize your learning journey.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: TextStyle(color: kcMediumGrey),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@ import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const GenderFormScreen({super.key});
|
const GenderFormScreen({super.key});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetGenderFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -63,30 +68,26 @@ class GenderFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildAgeGroups(viewModel)
|
_buildAgeGroups(viewModel)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Choose your gender?',
|
'Choose your gender?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'We’ll personalize your learning experience based on your gender.',
|
'We’ll personalize your learning experience based on your gender.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
Widget _buildAgeGroups(OnboardingViewModel viewModel) => ListView.builder(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
const LanguageGoalFormScreen(
|
const LanguageGoalFormScreen(
|
||||||
{super.key, required this.languageGoalController});
|
{super.key, required this.languageGoalController});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
languageGoalController.clear();
|
||||||
|
viewModel.resetLanguageGoalFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -75,7 +81,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildReasons(viewModel),
|
_buildReasons(viewModel),
|
||||||
if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
|
if (viewModel.showLanguageGoalTextBox) _buildReasonFormField(viewModel),
|
||||||
|
|
@ -91,26 +97,20 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'What’s your main goal for improving your English?',
|
'What’s your main goal for improving your English?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Your goal helps us tailor your learning journey.',
|
'Your goal helps us tailor your learning journey.',
|
||||||
style: TextStyle(
|
style: style14MG400,
|
||||||
color: kcMediumGrey,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
|
Widget _buildReasons(OnboardingViewModel viewModel) => ListView.builder(
|
||||||
|
|
@ -154,11 +154,7 @@ class LanguageGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
|
Widget _buildReasonValidator(OnboardingViewModel viewModel) => Text(
|
||||||
viewModel.languageGoalValidationMessage!,
|
viewModel.languageGoalValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
return Icons.book;
|
return Icons.book;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
viewModel.resetLearningGoalFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -87,9 +92,9 @@ class LearningGoalFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
onPop: viewModel.pop,
|
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
const OccupationFormScreen({super.key, required this.occupationController});
|
const OccupationFormScreen({super.key, required this.occupationController});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
occupationController.clear();
|
||||||
|
viewModel.resetOccupationFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -71,7 +77,7 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceLarge,
|
verticalSpaceLarge,
|
||||||
_buildOccupationFormField(viewModel),
|
_buildOccupationFormField(viewModel),
|
||||||
if (viewModel.hasOccupationValidationMessage &&
|
if (viewModel.hasOccupationValidationMessage &&
|
||||||
|
|
@ -84,23 +90,19 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.pop,
|
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'What’s your occupation?',
|
'What’s your occupation?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'We’ll personalize your learning experience based on your occupation.',
|
'We’ll personalize your learning experience based on your occupation.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildOccupationFormField(OnboardingViewModel viewModel) =>
|
Widget _buildOccupationFormField(OnboardingViewModel viewModel) =>
|
||||||
|
|
@ -120,11 +122,7 @@ class OccupationFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text(
|
Widget _buildOccupationValidator(OnboardingViewModel viewModel) => Text(
|
||||||
viewModel.occupationValidationMessage!,
|
viewModel.occupationValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
const TopicFormScreen({super.key, required this.topicController});
|
const TopicFormScreen({super.key, required this.topicController});
|
||||||
|
|
||||||
|
void _pop(OnboardingViewModel viewModel) {
|
||||||
|
topicController.clear();
|
||||||
|
viewModel.resetTopicFormScreen();
|
||||||
|
viewModel.goBack();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _next(OnboardingViewModel viewModel) async {
|
Future<void> _next(OnboardingViewModel viewModel) async {
|
||||||
FocusManager.instance.primaryFocus?.unfocus();
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
|
||||||
|
|
@ -45,8 +51,8 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(OnboardingViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.pop,
|
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _pop(viewModel),
|
||||||
onLanguage: () async => await viewModel.navigateToLanguage(),
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -82,7 +88,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTopics(viewModel),
|
_buildTopics(viewModel),
|
||||||
if (viewModel.showTopicTextBox) _buildTopicFormField(viewModel),
|
if (viewModel.showTopicTextBox) _buildTopicFormField(viewModel),
|
||||||
|
|
@ -97,18 +103,14 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Which topics interest you most?',
|
'Which topics interest you most?',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Your favorite topics help us create fun, relatable lessons.',
|
'Your favorite topics help us create fun, relatable lessons.',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildTopics(OnboardingViewModel viewModel) => ListView.builder(
|
Widget _buildTopics(OnboardingViewModel viewModel) => ListView.builder(
|
||||||
|
|
@ -150,11 +152,7 @@ class TopicFormScreen extends ViewModelWidget<OnboardingViewModel> {
|
||||||
|
|
||||||
Widget _buildTopicValidator(OnboardingViewModel viewModel) => Text(
|
Widget _buildTopicValidator(OnboardingViewModel viewModel) => Text(
|
||||||
viewModel.topicValidationMessage!,
|
viewModel.topicValidationMessage!,
|
||||||
style: const TextStyle(
|
style: style12R700,
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.red,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
Widget _buildContinueButtonWrapper(OnboardingViewModel viewModel) => Padding(
|
||||||
|
|
|
||||||
|
|
@ -164,28 +164,28 @@ class ProfileView extends StackedView<ProfileViewModel> {
|
||||||
Widget _buildDownloadsCard(ProfileViewModel viewModel) => ProfileCard(
|
Widget _buildDownloadsCard(ProfileViewModel viewModel) => ProfileCard(
|
||||||
icon: Icons.download,
|
icon: Icons.download,
|
||||||
title: 'My Downloads',
|
title: 'My Downloads',
|
||||||
subTitle: 'Access offline lessons and saved videos',
|
subtitle: 'Access offline lessons and saved videos',
|
||||||
onTap: () async => await viewModel.navigateToDownloads(),
|
onTap: () async => await viewModel.navigateToDownloads(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildProgressCard(ProfileViewModel viewModel) => ProfileCard(
|
Widget _buildProgressCard(ProfileViewModel viewModel) => ProfileCard(
|
||||||
title: 'My Progress',
|
title: 'My Progress',
|
||||||
icon: Icons.stacked_bar_chart,
|
icon: Icons.stacked_bar_chart,
|
||||||
subTitle: 'Track your achievements and learning streak',
|
subtitle: 'Track your achievements and learning streak',
|
||||||
onTap: () async => await viewModel.navigateToProgress(),
|
onTap: () async => await viewModel.navigateToProgress(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAccountCard(ProfileViewModel viewModel) => ProfileCard(
|
Widget _buildAccountCard(ProfileViewModel viewModel) => ProfileCard(
|
||||||
title: 'Account & Privacy',
|
title: 'Account & Privacy',
|
||||||
icon: Icons.privacy_tip_outlined,
|
icon: Icons.privacy_tip_outlined,
|
||||||
subTitle: 'Manage setting and app preference',
|
subtitle: 'Manage setting and app preference',
|
||||||
onTap: () async => await viewModel.navigateToAccountPrivacy(),
|
onTap: () async => await viewModel.navigateToAccountPrivacy(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSupportCard(ProfileViewModel viewModel) => ProfileCard(
|
Widget _buildSupportCard(ProfileViewModel viewModel) => ProfileCard(
|
||||||
title: 'Support',
|
title: 'Support',
|
||||||
icon: Icons.headphones,
|
icon: Icons.headphones,
|
||||||
subTitle: 'Get help through phone or Telegram',
|
subtitle: 'Get help through phone or Telegram',
|
||||||
onTap: () async => await viewModel.navigateToSupport(),
|
onTap: () async => await viewModel.navigateToSupport(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -700,5 +700,7 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildState(ProfileDetailViewModel viewModel) =>
|
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
|
// Update profile
|
||||||
Future<void> updateProfile() async => await runBusyFuture(_updateProfile());
|
Future<void> updateProfile() async => await runBusyFuture(_updateProfile(),
|
||||||
|
busyObject: StateObjects.profileUpdate);
|
||||||
|
|
||||||
Future<void> _updateProfile() async {
|
Future<void> _updateProfile() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
|
@ -228,8 +229,8 @@ class ProfileDetailViewModel extends ReactiveViewModel
|
||||||
await _apiService.completeProfile(_userData);
|
await _apiService.completeProfile(_userData);
|
||||||
if (response['status'] == ResponseStatus.success) {
|
if (response['status'] == ResponseStatus.success) {
|
||||||
await _authenticationService.updateUserData(_userData);
|
await _authenticationService.updateUserData(_userData);
|
||||||
showSuccessToast(response['message']);
|
|
||||||
pop();
|
pop();
|
||||||
|
showSuccessToast(response['message']);
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(response['message']);
|
showErrorToast(response['message']);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
||||||
title: viewModel.progresses[index]['title'],
|
title: viewModel.progresses[index]['title'],
|
||||||
color: viewModel.progresses[index]['color'],
|
color: viewModel.progresses[index]['color'],
|
||||||
status: viewModel.progresses[index]['status'],
|
status: viewModel.progresses[index]['status'],
|
||||||
subTitle: viewModel.progresses[index]['subTitle'],
|
subtitle: viewModel.progresses[index]['subtitle'],
|
||||||
isCompleted: viewModel.progresses[index]['isCompleted'],
|
isCompleted: viewModel.progresses[index]['isCompleted'],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -111,7 +111,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
||||||
required String title,
|
required String title,
|
||||||
required String icon,
|
required String icon,
|
||||||
required String status,
|
required String status,
|
||||||
required String subTitle,
|
required String subtitle,
|
||||||
required bool isCompleted,
|
required bool isCompleted,
|
||||||
required ProgressViewModel viewModel}) =>
|
required ProgressViewModel viewModel}) =>
|
||||||
CourseLevelCard(
|
CourseLevelCard(
|
||||||
|
|
@ -119,7 +119,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
|
||||||
title: title,
|
title: title,
|
||||||
color: color,
|
color: color,
|
||||||
status: status,
|
status: status,
|
||||||
subTitle: subTitle,
|
subtitle: subtitle,
|
||||||
isCompleted: isCompleted,
|
isCompleted: isCompleted,
|
||||||
onTap: viewModel.navigateToOngoingProgress,
|
onTap: viewModel.navigateToOngoingProgress,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class ProgressViewModel extends BaseViewModel {
|
||||||
'isCompleted': true,
|
'isCompleted': true,
|
||||||
'status': 'Completed',
|
'status': 'Completed',
|
||||||
'icon': 'assets/icons/b_1.svg',
|
'icon': 'assets/icons/b_1.svg',
|
||||||
'subTitle': 'You’ve mastered everyday English basics!',
|
'subtitle': 'You’ve mastered everyday English basics!',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Elementary',
|
'title': 'Elementary',
|
||||||
|
|
@ -23,7 +23,7 @@ class ProgressViewModel extends BaseViewModel {
|
||||||
'status': 'In Progress',
|
'status': 'In Progress',
|
||||||
'color': kcPrimaryColor,
|
'color': kcPrimaryColor,
|
||||||
'icon': 'assets/icons/b_1.svg',
|
'icon': 'assets/icons/b_1.svg',
|
||||||
'subTitle': 'Continue improving your conversations and fluency.',
|
'subtitle': 'Continue improving your conversations and fluency.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'title': 'Beginner',
|
'title': 'Beginner',
|
||||||
|
|
@ -31,7 +31,7 @@ class ProgressViewModel extends BaseViewModel {
|
||||||
'status': 'In Progress',
|
'status': 'In Progress',
|
||||||
'color': kcPrimaryColor,
|
'color': kcPrimaryColor,
|
||||||
'icon': 'assets/icons/b_1.svg',
|
'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:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked/stacked_annotations.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/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_email_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/register/screens/register_with_phone_number_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 {
|
class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
const RegisterView({Key? key}) : super(key: key);
|
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
|
@override
|
||||||
void onViewModelReady(RegisterViewModel viewModel) {
|
void onViewModelReady(RegisterViewModel viewModel) {
|
||||||
|
_initClearData();
|
||||||
syncFormWithViewModel(viewModel);
|
syncFormWithViewModel(viewModel);
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
@ -44,10 +83,8 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
|
|
||||||
Widget _buildRegisterScreensWrapper(RegisterViewModel viewModel) => PopScope(
|
Widget _buildRegisterScreensWrapper(RegisterViewModel viewModel) => PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvokedWithResult: (value, data) {
|
onPopInvokedWithResult: (value, data) =>
|
||||||
if (value) return;
|
_pop(value: value, viewModel: viewModel),
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack());
|
|
||||||
},
|
|
||||||
child: _buildScaffoldWrapper(viewModel));
|
child: _buildScaffoldWrapper(viewModel));
|
||||||
|
|
||||||
Widget _buildScaffoldWrapper(RegisterViewModel viewModel) => Scaffold(
|
Widget _buildScaffoldWrapper(RegisterViewModel viewModel) => Scaffold(
|
||||||
|
|
@ -55,8 +92,11 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
body: _buildScaffoldStack(viewModel),
|
body: _buildScaffoldStack(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack(
|
Widget _buildScaffoldStack(RegisterViewModel viewModel) => Stack(children: [
|
||||||
children: [_buildScaffold(viewModel), _buildBusyRegistration(viewModel)]);
|
_buildScaffold(viewModel),
|
||||||
|
_buildRegistrationState(viewModel),
|
||||||
|
_buildVerityOtpState(viewModel)
|
||||||
|
]);
|
||||||
|
|
||||||
Widget _buildScaffold(RegisterViewModel viewModel) => Column(
|
Widget _buildScaffold(RegisterViewModel viewModel) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -68,8 +108,8 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
|
|
||||||
Widget _buildAppBar(RegisterViewModel viewModel) => LargeAppBar(
|
Widget _buildAppBar(RegisterViewModel viewModel) => LargeAppBar(
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
onPop: viewModel.goBack,
|
|
||||||
showLanguageSelection: true,
|
showLanguageSelection: true,
|
||||||
|
onPop: () => _inAppPop(viewModel),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
Widget _buildExpandedBody(RegisterViewModel viewModel) =>
|
||||||
|
|
@ -81,7 +121,7 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildBody(RegisterViewModel viewModel) =>
|
Widget _buildBody(RegisterViewModel viewModel) =>
|
||||||
IndexedStack(index: viewModel.currentIndex, children: _buildScreens());
|
IndexedStack(index: viewModel.currentPage, children: _buildScreens());
|
||||||
|
|
||||||
List<Widget> _buildScreens() => [
|
List<Widget> _buildScreens() => [
|
||||||
_buildRegisterWithEmailScreen(),
|
_buildRegisterWithEmailScreen(),
|
||||||
|
|
@ -106,6 +146,13 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
|
||||||
passwordController: passwordController,
|
passwordController: passwordController,
|
||||||
confirmPasswordController: confirmPasswordController);
|
confirmPasswordController: confirmPasswordController);
|
||||||
|
|
||||||
Widget _buildBusyRegistration(RegisterViewModel viewModel) =>
|
Widget _buildRegistrationState(RegisterViewModel viewModel) =>
|
||||||
viewModel.isBusy ? const PageLoadingIndicator() : Container();
|
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>();
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
int _currentIndex = 0;
|
int _currentPage = 0;
|
||||||
|
|
||||||
int get currentIndex => _currentIndex;
|
int get currentPage => _currentPage;
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
bool _focusEmail = false;
|
bool _focusEmail = false;
|
||||||
|
|
@ -213,9 +213,35 @@ class RegisterViewModel extends FormViewModel {
|
||||||
_userData.clear();
|
_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
|
// In-app navigation
|
||||||
void goTo({required int page, RegistrationType? type}) {
|
void goTo({required int page, RegistrationType? type}) {
|
||||||
_currentIndex = page;
|
_currentPage = page;
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
_registrationType = type;
|
_registrationType = type;
|
||||||
}
|
}
|
||||||
|
|
@ -223,17 +249,17 @@ class RegisterViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void goBack() {
|
void goBack() {
|
||||||
if (_currentIndex == 1) {
|
if (_currentPage == 1) {
|
||||||
_currentIndex = 0;
|
_currentPage = 0;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
} else if (_currentIndex == 2) {
|
} else if (_currentPage == 2) {
|
||||||
_currentIndex = 0;
|
_currentPage = 0;
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
} else if (_currentIndex == 3) {
|
} else if (_currentPage == 3) {
|
||||||
if (_registrationType == RegistrationType.phone) {
|
if (_registrationType == RegistrationType.phone) {
|
||||||
_currentIndex = 1;
|
_currentPage = 1;
|
||||||
} else {
|
} else {
|
||||||
_currentIndex = 2;
|
_currentPage = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
|
|
@ -258,7 +284,8 @@ class RegisterViewModel extends FormViewModel {
|
||||||
// Remote api calls
|
// Remote api calls
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
Future<void> register() async => await runBusyFuture(_register());
|
Future<void> register() async =>
|
||||||
|
await runBusyFuture(_register(), busyObject: StateObjects.registration);
|
||||||
|
|
||||||
Future<void> _register() async {
|
Future<void> _register() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
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 {
|
Future<void> _verifyOtp() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
|
@ -296,7 +324,8 @@ class RegisterViewModel extends FormViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resend otp
|
// Resend otp
|
||||||
Future<void> resendOtp() async => await runBusyFuture(_resendOtp());
|
Future<void> resendOtp() async =>
|
||||||
|
await runBusyFuture(_resendOtp(), busyObject: StateObjects.resendOtp);
|
||||||
|
|
||||||
Future<void> _resendOtp() async {
|
Future<void> _resendOtp() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
_buildSubTitleWrapper(viewModel),
|
_buildSubtitleWrapper(viewModel),
|
||||||
verticalSpaceLarge,
|
verticalSpaceLarge,
|
||||||
_buildEmailFormField(viewModel),
|
_buildEmailFormField(viewModel),
|
||||||
if (viewModel.hasEmailValidationMessage && viewModel.focusEmail)
|
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(),
|
onTap: () async => await viewModel.replaceToLogin(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
List<Widget> _buildUpperColumnChildren(RegisterViewModel viewModel) => [
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
_buildSubTitleWrapper(viewModel),
|
_buildSubtitleWrapper(viewModel),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
_buildSubtitle(),
|
_buildSubtitle(),
|
||||||
verticalSpaceMedium,
|
verticalSpaceMedium,
|
||||||
|
|
@ -57,22 +57,18 @@ class RegisterWithPhoneNumberScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
_buildPhoneNumberValidatorWrapper(viewModel),
|
_buildPhoneNumberValidatorWrapper(viewModel),
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildTitle() => const Text(
|
Widget _buildTitle() => Text(
|
||||||
'Create an Account',
|
'Create an Account',
|
||||||
style: TextStyle(
|
style: style25DG600,
|
||||||
fontSize: 25,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
Widget _buildSubtitleWrapper(RegisterViewModel viewModel) => LoginAccount(
|
||||||
onTap: () async => await viewModel.replaceToLogin(),
|
onTap: () async => await viewModel.replaceToLogin(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubtitle() => const Text(
|
Widget _buildSubtitle() => Text(
|
||||||
'Enter your phone number. We will send you a confirmation code there',
|
'Enter your phone number. We will send you a confirmation code there',
|
||||||
style: TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildPhoneNumberWrapper(RegisterViewModel viewModel) => Row(
|
Widget _buildPhoneNumberWrapper(RegisterViewModel viewModel) => Row(
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,8 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
|
||||||
|
|
||||||
Widget _buildResendButton(RegisterViewModel viewModel) => TextButton(
|
Widget _buildResendButton(RegisterViewModel viewModel) => TextButton(
|
||||||
onPressed: () async => await viewModel.resendOtp(),
|
onPressed: () async => await viewModel.resendOtp(),
|
||||||
|
style:
|
||||||
|
const ButtonStyle(padding: WidgetStatePropertyAll(EdgeInsets.zero)),
|
||||||
child: _buildResendText());
|
child: _buildResendText());
|
||||||
|
|
||||||
Widget _buildResendText() => Text(
|
Widget _buildResendText() => Text(
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class CourseLevelCard extends StatelessWidget {
|
||||||
final String icon;
|
final String icon;
|
||||||
final String title;
|
final String title;
|
||||||
final String status;
|
final String status;
|
||||||
final String subTitle;
|
final String subtitle;
|
||||||
final bool isCompleted;
|
final bool isCompleted;
|
||||||
final GestureTapCallback? onTap;
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ class CourseLevelCard extends StatelessWidget {
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.color,
|
required this.color,
|
||||||
required this.status,
|
required this.status,
|
||||||
required this.subTitle,
|
required this.subtitle,
|
||||||
required this.isCompleted,
|
required this.isCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ class CourseLevelCard extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildSubTitle() => Expanded(
|
Widget _buildSubTitle() => Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
subTitle,
|
subtitle,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
style: const TextStyle(color: kcMediumGrey),
|
style: const TextStyle(color: kcMediumGrey),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
|
|
||||||
class CustomLargeRadioButton extends StatelessWidget {
|
class CustomLargeRadioButton extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
|
|
@ -49,7 +50,7 @@ class CustomLargeRadioButton extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildButtonRowChildren() =>
|
List<Widget> _buildButtonRowChildren() =>
|
||||||
[_buildIconSectionWrapper(), _buildTitle(), _buildSubTitle()];
|
[_buildIconSectionWrapper(), _buildTitle(), _buildSubtitle()];
|
||||||
|
|
||||||
Widget _buildIconSectionWrapper() => Row(
|
Widget _buildIconSectionWrapper() => Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|
@ -67,14 +68,10 @@ class CustomLargeRadioButton extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: style18DG600,
|
||||||
fontSize: 18,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => Text(
|
Widget _buildSubtitle() => Text(
|
||||||
subtitle,
|
subtitle,
|
||||||
style: const TextStyle(color: kcMediumGrey),
|
style: const TextStyle(color: kcMediumGrey),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class LearnAppBar extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
List<Widget> _buildGreetingChildren() =>
|
List<Widget> _buildGreetingChildren() =>
|
||||||
[_buildGreetingTitle(), _buildSubTitle()];
|
[_buildGreetingTitle(), _buildSubtitle()];
|
||||||
|
|
||||||
Widget _buildGreetingTitle() => Text.rich(
|
Widget _buildGreetingTitle() => Text.rich(
|
||||||
TextSpan(text: 'Hello,', style: style14DG600, children: [
|
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?',
|
'Ready to keep learning English today?',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: style14DG400,
|
style: style14DG400,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
class ProfileCard extends StatelessWidget {
|
class ProfileCard extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final String subTitle;
|
final String subtitle;
|
||||||
final GestureTapCallback? onTap;
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
const ProfileCard(
|
const ProfileCard(
|
||||||
|
|
@ -13,7 +13,7 @@ class ProfileCard extends StatelessWidget {
|
||||||
this.onTap,
|
this.onTap,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subTitle});
|
required this.subtitle});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _buildContainerWrapper();
|
Widget build(BuildContext context) => _buildContainerWrapper();
|
||||||
|
|
@ -43,7 +43,7 @@ class ProfileCard extends StatelessWidget {
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildTitle(),
|
_buildTitle(),
|
||||||
verticalSpaceSmall,
|
verticalSpaceSmall,
|
||||||
_buildSubTitle()
|
_buildSubtitle()
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget _buildIcon() => Icon(
|
Widget _buildIcon() => Icon(
|
||||||
|
|
@ -54,15 +54,11 @@ class ProfileCard extends StatelessWidget {
|
||||||
|
|
||||||
Widget _buildTitle() => Text(
|
Widget _buildTitle() => Text(
|
||||||
title,
|
title,
|
||||||
style: const TextStyle(
|
style: style16DG600,
|
||||||
fontSize: 16,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildSubTitle() => Text(
|
Widget _buildSubtitle() => Text(
|
||||||
subTitle,
|
subtitle,
|
||||||
style: const TextStyle(color: kcMediumGrey),
|
style: style14MG400,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1016,6 +1016,33 @@ class MockApiService extends _i1.Mock implements _i12.ApiService {
|
||||||
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
_i8.Future<Map<String, dynamic>>.value(<String, dynamic>{}),
|
||||||
) as _i8.Future<Map<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
|
@override
|
||||||
_i8.Future<Map<String, dynamic>> getProfileStatus(_i11.UserModel? user) =>
|
_i8.Future<Map<String, dynamic>> getProfileStatus(_i11.UserModel? user) =>
|
||||||
(super.noSuchMethod(
|
(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