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