- fix(auth): Fix how user data in fetched from both local and remote

storage.
- feat(learn): Complete all learn module UI
This commit is contained in:
BisratHailu 2026-02-19 10:43:31 +03:00
parent 1c8b54fe69
commit d75ed8c7c7
71 changed files with 1590 additions and 1969 deletions

View File

@ -412,8 +412,13 @@ class StackedRouter extends _i1.RouterBase {
); );
}, },
_i28.LearnPracticeView: (data) { _i28.LearnPracticeView: (data) {
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
return _i29.MaterialPageRoute<dynamic>( return _i29.MaterialPageRoute<dynamic>(
builder: (context) => const _i28.LearnPracticeView(), builder: (context) => _i28.LearnPracticeView(
key: args.key,
title: args.title,
subtitle: args.subtitle,
buttonLabel: args.buttonLabel),
settings: data, settings: data,
); );
}, },
@ -507,6 +512,45 @@ class FailureViewArguments {
} }
} }
class LearnPracticeViewArguments {
const LearnPracticeViewArguments({
this.key,
required this.title,
required this.subtitle,
required this.buttonLabel,
});
final _i29.Key? key;
final String title;
final String subtitle;
final String buttonLabel;
@override
String toString() {
return '{"key": "$key", "title": "$title", "subtitle": "$subtitle", "buttonLabel": "$buttonLabel"}';
}
@override
bool operator ==(covariant LearnPracticeViewArguments other) {
if (identical(this, other)) return true;
return other.key == key &&
other.title == title &&
other.subtitle == subtitle &&
other.buttonLabel == buttonLabel;
}
@override
int get hashCode {
return key.hashCode ^
title.hashCode ^
subtitle.hashCode ^
buttonLabel.hashCode;
}
}
extension NavigatorStateExtension on _i30.NavigationService { extension NavigatorStateExtension on _i30.NavigationService {
Future<dynamic> navigateToHomeView([ Future<dynamic> navigateToHomeView([
int? routerId, int? routerId,
@ -881,14 +925,23 @@ extension NavigatorStateExtension on _i30.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> navigateToLearnPracticeView([ Future<dynamic> navigateToLearnPracticeView({
_i29.Key? key,
required String title,
required String subtitle,
required String buttonLabel,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)? Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition, transition,
]) async { }) async {
return navigateTo<dynamic>(Routes.learnPracticeView, return navigateTo<dynamic>(Routes.learnPracticeView,
arguments: LearnPracticeViewArguments(
key: key,
title: title,
subtitle: subtitle,
buttonLabel: buttonLabel),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,
@ -1268,14 +1321,23 @@ extension NavigatorStateExtension on _i30.NavigationService {
transition: transition); transition: transition);
} }
Future<dynamic> replaceWithLearnPracticeView([ Future<dynamic> replaceWithLearnPracticeView({
_i29.Key? key,
required String title,
required String subtitle,
required String buttonLabel,
int? routerId, int? routerId,
bool preventDuplicates = true, bool preventDuplicates = true,
Map<String, String>? parameters, Map<String, String>? parameters,
Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)? Widget Function(BuildContext, Animation<double>, Animation<double>, Widget)?
transition, transition,
]) async { }) async {
return replaceWith<dynamic>(Routes.learnPracticeView, return replaceWith<dynamic>(Routes.learnPracticeView,
arguments: LearnPracticeViewArguments(
key: key,
title: title,
subtitle: subtitle,
buttonLabel: buttonLabel),
id: routerId, id: routerId,
preventDuplicates: preventDuplicates, preventDuplicates: preventDuplicates,
parameters: parameters, parameters: parameters,

View File

@ -12,12 +12,10 @@ class UserModel {
final String? country; final String? country;
final String? occupation; final String? occupation;
final bool? userInfoLoaded; final bool? userInfoLoaded;
@JsonKey(name: 'user_id') @JsonKey(name: 'user_id')
final int? userId; final int? userId;
@ -55,7 +53,7 @@ class UserModel {
this.accessToken, this.accessToken,
this.refreshToken, this.refreshToken,
this.profilePicture, this.profilePicture,
this.userInfoLoaded , this.userInfoLoaded,
this.profileCompleted, this.profileCompleted,
}); });

View File

@ -19,8 +19,8 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
accessToken: json['access_token'] as String?, accessToken: json['access_token'] as String?,
refreshToken: json['refresh_token'] as String?, refreshToken: json['refresh_token'] as String?,
profilePicture: json['profile_picture_url'] as String?, profilePicture: json['profile_picture_url'] as String?,
userInfoLoaded: json['userInfoLoaded'] as bool?,
profileCompleted: json['profile_completed'] as bool?, profileCompleted: json['profile_completed'] as bool?,
userInfoLoaded: json['userInfoLoaded'] as bool? ?? false,
); );
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{

View File

@ -11,7 +11,8 @@ class ApiService {
final _service = locator<DioService>(); final _service = locator<DioService>();
// Register // Register
Future<Map<String, dynamic>> registerWithEmail(Map<String, dynamic> data) async { Future<Map<String, dynamic>> registerWithEmail(
Map<String, dynamic> data) async {
try { try {
Response response = await _service.dio.post( Response response = await _service.dio.post(
'$kBaseUrl/$kUserUrl/$kRegisterUrl', '$kBaseUrl/$kUserUrl/$kRegisterUrl',

View File

@ -37,7 +37,6 @@ class AuthenticationService with ListenableServiceMixin {
await _secureService.setString('refreshToken', refresh); await _secureService.setString('refreshToken', refresh);
} }
Future<void> saveUserCredential(Map<String, dynamic> data) async { Future<void> saveUserCredential(Map<String, dynamic> data) async {
await _secureService.setInt('userId', data['userId']); await _secureService.setInt('userId', data['userId']);
await _secureService.setString('accessToken', data['accessToken']); await _secureService.setString('accessToken', data['accessToken']);
@ -71,8 +70,8 @@ class AuthenticationService with ListenableServiceMixin {
notifyListeners(); notifyListeners();
} }
Future<void> saveProfileImage(String image) async { Future<void> saveProfilePicture(String image) async {
await _secureService.setString('profileImage', image); await _secureService.setString('profilePicture', image);
_user = UserModel( _user = UserModel(
email: _user?.email, email: _user?.email,
gender: _user?.gender, gender: _user?.gender,
@ -87,7 +86,7 @@ class AuthenticationService with ListenableServiceMixin {
refreshToken: _user?.refreshToken, refreshToken: _user?.refreshToken,
profileCompleted: _user?.profileCompleted, profileCompleted: _user?.profileCompleted,
userInfoLoaded: _user?.userInfoLoaded ?? false, userInfoLoaded: _user?.userInfoLoaded ?? false,
profilePicture: await _secureService.getString('profileImage'), profilePicture: await _secureService.getString('profilePicture'),
); );
notifyListeners(); notifyListeners();
@ -175,8 +174,8 @@ class AuthenticationService with ListenableServiceMixin {
occupation: await _secureService.getString('occupation'), occupation: await _secureService.getString('occupation'),
accessToken: await _secureService.getString('accessToken'), accessToken: await _secureService.getString('accessToken'),
refreshToken: await _secureService.getString('refreshToken'), refreshToken: await _secureService.getString('refreshToken'),
profilePicture: await _secureService.getString('profileImage'),
userInfoLoaded: await _secureService.getBool('userInfoLoaded'), userInfoLoaded: await _secureService.getBool('userInfoLoaded'),
profilePicture: await _secureService.getString('profilePicture'),
profileCompleted: await _secureService.getBool('profileCompleted'), profileCompleted: await _secureService.getBool('profileCompleted'),
); );
return _user; return _user;

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
const Color kcBlack = Colors.black; const Color kcBlack = Colors.black;
const Color kcRed = Color(0xffFF4C4C); const Color kcRed = Color(0xffFF4C4C);
const Color kcBlue = Color(0xff135BEC);
const Color kcGreen = Color(0xFF1DE964); const Color kcGreen = Color(0xFF1DE964);
const Color kcBackgroundColor = kcWhite; const Color kcBackgroundColor = kcWhite;
const Color kcWhite = Color(0xFFFFFFFF); const Color kcWhite = Color(0xFFFFFFFF);

View File

@ -11,6 +11,7 @@ enum ProficiencyLevels { a1, a2, b1, b2, none }
// State object // State object
enum StateObjects { enum StateObjects {
homeView,
verifyOtp, verifyOtp,
resendOtp, resendOtp,
profileImage, profileImage,

View File

@ -178,6 +178,12 @@ TextStyle style18P600 = const TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
TextStyle style16W600 = const TextStyle(
fontSize: 16,
color: kcWhite,
fontWeight: FontWeight.w600,
);
TextStyle style18W600 = const TextStyle( TextStyle style18W600 = const TextStyle(
fontSize: 18, fontSize: 18,
color: kcWhite, color: kcWhite,
@ -190,6 +196,12 @@ TextStyle style25W600 = const TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
TextStyle style12RP600 = const TextStyle(
fontSize: 12,
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
);
TextStyle style12R700 = const TextStyle( TextStyle style12R700 = const TextStyle(
fontSize: 12, fontSize: 12,
@ -197,10 +209,19 @@ TextStyle style12R700 = const TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
); );
TextStyle style12DG400 = const TextStyle(
fontSize: 12,
color: kcDarkGrey,
);
TextStyle style14P400 = const TextStyle( TextStyle style14P400 = const TextStyle(
color: kcPrimaryColor, color: kcPrimaryColor,
); );
TextStyle style14B400 = const TextStyle(
color: kcBlue,
);
TextStyle style14P600 = const TextStyle( TextStyle style14P600 = const TextStyle(
color: kcPrimaryColor, color: kcPrimaryColor,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -224,6 +245,12 @@ TextStyle style16DG600 = const TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
); );
TextStyle style16B600 = const TextStyle(
fontSize: 16,
color: kcBlue,
fontWeight: FontWeight.w600,
);
TextStyle style18DG500 = const TextStyle( TextStyle style18DG500 = const TextStyle(
fontSize: 18, fontSize: 18,
color: kcDarkGrey, color: kcDarkGrey,

View File

@ -57,8 +57,9 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
); );
Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar( Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar(
title: 'Account Privacy', showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
title: 'Account Privacy',
); );
Widget _buildContentWrapper(AccountPrivacyViewModel viewModel) => Widget _buildContentWrapper(AccountPrivacyViewModel viewModel) =>

View File

@ -235,7 +235,7 @@ class AssessmentViewModel extends BaseViewModel {
await _navigationService.navigateToLanguageView(); await _navigationService.navigateToLanguageView();
Future<void> replaceWithHome() async => Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView()); await _navigationService.clearStackAndShow(Routes.homeView);
// Remote api call // Remote api call
Future<void> getAssessments() async => await runBusyFuture(_getAssessments()); Future<void> getAssessments() async => await runBusyFuture(_getAssessments());

View File

@ -50,8 +50,9 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
); );
Widget _buildAppbar(CallSupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(CallSupportViewModel viewModel) => SmallAppBar(
title: 'Call Support', showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
title: 'Call Support',
); );
Widget _buildExpandedColumn(CallSupportViewModel viewModel) => Widget _buildExpandedColumn(CallSupportViewModel viewModel) =>

View File

@ -54,8 +54,9 @@ class DownloadsView extends StackedView<DownloadsViewModel> {
]; ];
Widget _buildAppbar(DownloadsViewModel viewModel) => SmallAppBar( Widget _buildAppbar(DownloadsViewModel viewModel) => SmallAppBar(
title: 'Offline Downloads',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
title: 'Offline Downloads',
); );
Widget _buildContentWrapper(DownloadsViewModel viewModel) => Widget _buildContentWrapper(DownloadsViewModel viewModel) =>

View File

@ -29,8 +29,6 @@ class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
confirmPasswordController.clear(); confirmPasswordController.clear();
} }
void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) {
if (viewModel.currentPage == 0) { if (viewModel.currentPage == 0) {
emailController.clear(); emailController.clear();
@ -77,10 +75,6 @@ class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
_pop(value: value, viewModel: viewModel), _pop(value: value, viewModel: viewModel),
child: _buildBody(viewModel)); child: _buildBody(viewModel));
Widget _buildBody(ForgetPasswordViewModel viewModel) => Widget _buildBody(ForgetPasswordViewModel viewModel) =>
IndexedStack(index: viewModel.currentPage, children: _buildScreens()); IndexedStack(index: viewModel.currentPage, children: _buildScreens());
@ -96,6 +90,4 @@ class ForgetPasswordView extends StackedView<ForgetPasswordViewModel>
passwordController: passwordController, passwordController: passwordController,
resetCodeController: resetCodeController, resetCodeController: resetCodeController,
confirmPasswordController: confirmPasswordController); confirmPasswordController: confirmPasswordController);
} }

View File

@ -3,6 +3,7 @@ import 'package:stacked_services/stacked_services.dart';
import 'package:yimaru_app/ui/views/login/login_view.dart'; import 'package:yimaru_app/ui/views/login/login_view.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../app/app.router.dart';
import '../../../services/api_service.dart'; import '../../../services/api_service.dart';
import '../../../services/status_checker_service.dart'; import '../../../services/status_checker_service.dart';
import '../../common/enmus.dart'; import '../../common/enmus.dart';
@ -190,7 +191,7 @@ class ForgetPasswordViewModel extends FormViewModel {
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> replaceWithLogin() async => Future<void> replaceWithLogin() async =>
await _navigationService.clearStackAndShowView(const LoginView()); await _navigationService.clearStackAndShow(Routes.loginView);
// Remote api calls // Remote api calls

View File

@ -20,10 +20,11 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
required this.emailController, required this.emailController,
}); });
Widget getPadding(context) {
Widget getPadding(context){ double half = screenHeight(context) / 2;
double half = screenHeight(context)/2; return SizedBox(
return SizedBox(height: half + 375 - half,); height: half + 375 - half,
);
} }
void _inAppPop(ForgetPasswordViewModel viewModel) { void _inAppPop(ForgetPasswordViewModel viewModel) {
@ -32,9 +33,8 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
} }
void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) {
emailController.clear(); emailController.clear();
viewModel.resetRequestResetCodeScreen(); viewModel.resetRequestResetCodeScreen();
} }
Future<void> _addUserData(ForgetPasswordViewModel viewModel) async { Future<void> _addUserData(ForgetPasswordViewModel viewModel) async {
@ -48,69 +48,89 @@ class RequestCodeScreen extends ViewModelWidget<ForgetPasswordViewModel> {
await viewModel.requestResetCode(); await viewModel.requestResetCode();
} }
@override @override
Widget build(BuildContext context, ForgetPasswordViewModel viewModel) => Widget build(BuildContext context, ForgetPasswordViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context, Widget _buildScaffoldWrapper(
required ForgetPasswordViewModel viewModel}) => Scaffold( {required BuildContext context,
backgroundColor: kcBackgroundColor, required ForgetPasswordViewModel viewModel}) =>
body: _buildScaffoldStack(context: context, viewModel: viewModel), Scaffold(
); backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack( {required BuildContext context, Widget _buildScaffoldStack(
required ForgetPasswordViewModel viewModel}) => Stack( {required BuildContext context,
children: [ required ForgetPasswordViewModel viewModel}) =>
_buildScaffold(context: context,viewModel: viewModel), Stack(
_buildRequestResetCodeState(viewModel), children: [
], _buildScaffold(context: context, viewModel: viewModel),
); _buildRequestResetCodeState(viewModel),
],
);
Widget _buildScaffold( {required BuildContext context, Widget _buildScaffold(
required ForgetPasswordViewModel viewModel}) => Column( {required BuildContext context,
crossAxisAlignment: CrossAxisAlignment.start, required ForgetPasswordViewModel viewModel}) =>
children: _buildScaffoldChildren(context: context, viewModel: viewModel), Column(
); crossAxisAlignment: CrossAxisAlignment.start,
children:
_buildScaffoldChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildScaffoldChildren( {required BuildContext context, List<Widget> _buildScaffoldChildren(
required ForgetPasswordViewModel viewModel}) => {required BuildContext context,
[_buildAppBar(viewModel), _buildExpandedBody(context: context, viewModel: viewModel)]; required ForgetPasswordViewModel viewModel}) =>
[
_buildAppBar(viewModel),
_buildExpandedBody(context: context, viewModel: viewModel)
];
Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar(
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
); );
Widget _buildExpandedBody( {required BuildContext context, Widget _buildExpandedBody(
required ForgetPasswordViewModel viewModel}) => {required BuildContext context,
Expanded(child: _buildColumnScroller(context: context, viewModel: viewModel)); required ForgetPasswordViewModel viewModel}) =>
Expanded(
child: _buildColumnScroller(context: context, viewModel: viewModel));
Widget _buildColumnScroller( {required BuildContext context, Widget _buildColumnScroller(
required ForgetPasswordViewModel viewModel}) => {required BuildContext context,
required ForgetPasswordViewModel viewModel}) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(context: context, viewModel: viewModel), child: _buildBodyWrapper(context: context, viewModel: viewModel),
); );
Widget _buildBodyWrapper( {required BuildContext context, Widget _buildBodyWrapper(
required ForgetPasswordViewModel viewModel}) => Padding( {required BuildContext context,
padding: const EdgeInsets.symmetric(horizontal: 15), required ForgetPasswordViewModel viewModel}) =>
child: _buildBody(context: context, viewModel: 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(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyChildren( {required BuildContext context,
required ForgetPasswordViewModel viewModel}) =>
[_buildUpperColumn(viewModel),getPadding(context), _buildContinueButtonWrapper(viewModel)];
Widget _buildBody(
{required BuildContext context,
required ForgetPasswordViewModel viewModel}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyChildren(
{required BuildContext context,
required ForgetPasswordViewModel viewModel}) =>
[
_buildUpperColumn(viewModel),
getPadding(context),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildUpperColumn(ForgetPasswordViewModel viewModel) => Column( Widget _buildUpperColumn(ForgetPasswordViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -31,12 +31,10 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
} }
void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) { void _clearDataOnNavigation(ForgetPasswordViewModel viewModel) {
passwordController.clear();
passwordController.clear(); resetCodeController.clear();
resetCodeController.clear(); confirmPasswordController.clear();
confirmPasswordController.clear(); viewModel.resetResetPasswordScreen();
viewModel.resetResetPasswordScreen();
} }
Future<void> _reset(ForgetPasswordViewModel viewModel) async { Future<void> _reset(ForgetPasswordViewModel viewModel) async {
@ -55,56 +53,56 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
Widget build(BuildContext context, ForgetPasswordViewModel viewModel) => Widget build(BuildContext context, ForgetPasswordViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context, Widget _buildScaffoldWrapper(
required ForgetPasswordViewModel viewModel}) => Scaffold( {required BuildContext context,
backgroundColor: kcBackgroundColor, required ForgetPasswordViewModel viewModel}) =>
body: _buildScaffoldStack(context: context, viewModel: viewModel), Scaffold(
); backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack( {required BuildContext context, Widget _buildScaffoldStack(
required ForgetPasswordViewModel viewModel}) => Stack( {required BuildContext context,
children: [ required ForgetPasswordViewModel viewModel}) =>
_buildScaffold(viewModel), Stack(
_buildResetPasswordState(viewModel), children: [
], _buildScaffold(viewModel),
); _buildResetPasswordState(viewModel),
],
);
Widget _buildScaffold( ForgetPasswordViewModel viewModel) => Column( Widget _buildScaffold(ForgetPasswordViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildScaffoldChildren(viewModel), children: _buildScaffoldChildren(viewModel),
); );
List<Widget> _buildScaffoldChildren( ForgetPasswordViewModel viewModel) => List<Widget> _buildScaffoldChildren(ForgetPasswordViewModel viewModel) =>
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)]; [_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar( Widget _buildAppBar(ForgetPasswordViewModel viewModel) => LargeAppBar(
showBackButton: true, showBackButton: true,
showLanguageSelection: true, showLanguageSelection: true,
onPop: () => _inAppPop(viewModel), onPop: () => _inAppPop(viewModel),
); );
Widget _buildExpandedBody( ForgetPasswordViewModel viewModel) => Widget _buildExpandedBody(ForgetPasswordViewModel viewModel) =>
Expanded(child: _buildColumnScroller(viewModel)); Expanded(child: _buildColumnScroller(viewModel));
Widget _buildColumnScroller( ForgetPasswordViewModel viewModel) => Widget _buildColumnScroller(ForgetPasswordViewModel viewModel) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(viewModel), child: _buildBodyWrapper(viewModel),
); );
Widget _buildBodyWrapper( ForgetPasswordViewModel viewModel) => Padding( Widget _buildBodyWrapper(ForgetPasswordViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel), child: _buildBody(viewModel),
); );
Widget _buildBody(ForgetPasswordViewModel viewModel) => Column( Widget _buildBody(ForgetPasswordViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(ForgetPasswordViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(ForgetPasswordViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn/learn_view.dart'; import 'package:yimaru_app/ui/views/learn/learn_view.dart';
import 'package:yimaru_app/ui/views/profile/profile_view.dart'; import 'package:yimaru_app/ui/views/profile/profile_view.dart';
import 'package:yimaru_app/ui/views/startup/startup_view.dart'; import 'package:yimaru_app/ui/views/startup/startup_view.dart';
@ -16,19 +18,22 @@ class HomeView extends StackedView<HomeViewModel> {
@override @override
void onViewModelReady(HomeViewModel viewModel) async { void onViewModelReady(HomeViewModel viewModel) async {
await viewModel.getProfileStatus(); await _init(viewModel);
await viewModel.getProfileData();
super.onViewModelReady(viewModel); super.onViewModelReady(viewModel);
} }
Future<void> _init(HomeViewModel viewModel) async =>
await viewModel.initialize();
@override @override
Widget builder( Widget builder(
BuildContext context, HomeViewModel viewModel, Widget? child) => BuildContext context, HomeViewModel viewModel, Widget? child) =>
_buildScaffoldWrapper(viewModel); _buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(HomeViewModel viewModel) => viewModel.isBusy Widget _buildScaffoldWrapper(HomeViewModel viewModel) =>
? const StartupView(label: 'Checking user info') viewModel.busy(StateObjects.homeView)
: _buildScaffold(viewModel); ? const StartupView(label: 'Checking user info')
: _buildScaffold(viewModel);
Widget _buildScaffold(HomeViewModel viewModel) => Scaffold( Widget _buildScaffold(HomeViewModel viewModel) => Scaffold(
body: getViewForIndex(viewModel.currentIndex), body: getViewForIndex(viewModel.currentIndex),

View File

@ -37,6 +37,7 @@ class HomeViewModel extends ReactiveViewModel {
int get currentIndex => _currentIndex; int get currentIndex => _currentIndex;
// Bottom navigation
void setCurrentIndex(int index) { void setCurrentIndex(int index) {
_currentIndex = index; _currentIndex = index;
rebuildUi(); rebuildUi();
@ -63,72 +64,76 @@ class HomeViewModel extends ReactiveViewModel {
// Navigation // Navigation
Future<void> replaceWithFailure() async => Future<void> replaceWithFailure() async =>
await _navigationService.clearStackAndShowView( await _navigationService.clearStackAndShow(Routes.failureView,
const FailureView(label: 'Check your internet connection to proceed'), arguments: 'Check your internet connection to proceed');
);
Future<void> replaceWithOnboarding() async => Future<void> replaceWithOnboarding() async =>
await _navigationService.replaceWithOnboardingView(); await _navigationService.replaceWithOnboardingView();
// Remote api calls // Remote api calls
// Initialize user data
Future<void> initialize() async =>
await runBusyFuture(_initialize(), busyObject: StateObjects.homeView);
Future<void> _initialize() async {
await _getProfileStatus();
await _getProfileData();
}
// Profile status
Future<void> _getProfileStatus() async {
Map<String, dynamic> response = {};
if (_user?.profileCompleted == null) {
if (await _statusChecker.checkConnection()) {
print('BREAK-POINT 1');
response = await _apiService.getProfileStatus(_user);
} else {
print('BREAK-POINT 2');
await replaceWithFailure();
}
} else if (!(_user?.profileCompleted ?? false)) {
print('BREAK-POINT 3');
response = {'data': false, 'status': ResponseStatus.success};
} else {
print('BREAK-POINT 4');
response = {'data': true, 'status': ResponseStatus.success};
}
if (response['status'] == ResponseStatus.success && !response['data']) {
print('BREAK-POINT 5');
await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
print('BREAK-POINT 6');
await saveProfileStatus(response['data']);
}
}
// Profile data // Profile data
Future<void> getProfileData() async => await runBusyFuture(_getProfileData());
Future<void> _getProfileData() async { Future<void> _getProfileData() async {
print('RESPONSE FOR USER DATA ${_user?.firstName}');
if (!(_user?.userInfoLoaded ?? false)) { if (!(_user?.userInfoLoaded ?? false)) {
print('RESPONSE FOR USER DATA 1'); Map<String, dynamic> response = {};
if (await _statusChecker.checkConnection()) { print('BREAK-POINT: Profile-Completed ${_user?.profileCompleted}');
Map<String, dynamic> response = {};
if (_user?.profileCompleted != null && if (_user?.profileCompleted != null &&
(_user?.profileCompleted ?? false)) { (_user?.profileCompleted ?? false)) {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileData(_user?.userId); response = await _apiService.getProfileData(_user?.userId);
if (response['status'] == ResponseStatus.success) { if (response['status'] == ResponseStatus.success) {
UserModel user = response['data'] as UserModel; print('BREAK-POINT 8');
String image = UserModel user = response['data'] as UserModel;
await _imageDownloaderService.downloader(user.profilePicture);
await _authenticationService.saveUserData( String image =
image: image, data: user); await _imageDownloaderService.downloader(user.profilePicture);
}
await _authenticationService.saveUserData(image: image, data: user);
} }
} }
} }
} }
} }
// Profile status
Future<void> getProfileStatus() async =>
await runBusyFuture(_getProfileStatus());
Future<void> _getProfileStatus() async {
if (await _statusChecker.checkConnection()) {
Map<String, dynamic> response = {};
if (_user?.profileCompleted == null) {
if (await _statusChecker.checkConnection()) {
response = await _apiService.getProfileStatus(_user);
} else {
await replaceWithFailure();
}
} else if (!(_user?.profileCompleted ?? false)) {
response = {'data': false, 'status': ResponseStatus.success};
} else {
response = {'data': true, 'status': ResponseStatus.success};
}
if (response['status'] == ResponseStatus.success && !response['data']) {
await replaceWithOnboarding();
} else if (response['status'] == ResponseStatus.success &&
response['data']) {
await saveProfileStatus(response['data']);
}
}
}
} }

View File

@ -72,6 +72,7 @@ class LanguageView extends StackedView<LanguageViewModel> {
); );
Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar( Widget _buildAppbar(LanguageViewModel viewModel) => SmallAppBar(
showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
title: 'Language Preference', title: 'Language Preference',
); );

View File

@ -51,6 +51,7 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
Widget _buildAppBar(LearnLessonViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLessonViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
); );
Widget _buildLevelsColumnWrapper(LearnLessonViewModel viewModel) => Widget _buildLevelsColumnWrapper(LearnLessonViewModel viewModel) =>
@ -122,6 +123,8 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
thumbnail: viewModel.lessons[index]['thumbnail'], thumbnail: viewModel.lessons[index]['thumbnail'],
onLessonTap: () async => onLessonTap: () async =>
await viewModel.navigateToLearnLessonDetail(), await viewModel.navigateToLearnLessonDetail(),
onPracticeTap: () async => await viewModel.navigateToLearnPractice(),
), ),
); );
@ -130,11 +133,13 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
required String thumbnail, required String thumbnail,
GestureTapCallback? onLessonTap, GestureTapCallback? onLessonTap,
required ProgressStatuses status, required ProgressStatuses status,
GestureTapCallback? onPracticeTap,
}) => }) =>
LearnLessonTile( LearnLessonTile(
title: title, title: title,
status: status, status: status,
thumbnail: thumbnail, thumbnail: thumbnail,
onLessonTap: onLessonTap, onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,
); );
} }

View File

@ -34,4 +34,12 @@ class LearnLessonViewModel extends BaseViewModel {
Future<void> navigateToLearnLessonDetail() async => Future<void> navigateToLearnLessonDetail() async =>
await _navigationService.navigateToLearnLessonDetailView(); await _navigationService.navigateToLearnLessonDetailView();
Future<void> navigateToLearnPractice() async =>
await _navigationService.navigateToLearnPracticeView(
buttonLabel: 'Start Practice',
title: 'Let \'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
} }

View File

@ -15,13 +15,11 @@ import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> { class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
const LearnLessonDetailView({Key? key}) : super(key: key); const LearnLessonDetailView({Key? key}) : super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
Future<void> _navigate(LearnLessonDetailViewModel viewModel)async{
await viewModel.pause(); await viewModel.pause();
await viewModel.navigateToLearnPractice(); await viewModel.navigateToLearnPractice();
} }
// @override // @override
// void onDispose(LearnLessonDetailViewModel viewModel) { // void onDispose(LearnLessonDetailViewModel viewModel) {
// print('DISPOSED'); // print('DISPOSED');
@ -70,6 +68,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
Widget _buildAppBar(LearnLessonDetailViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLessonDetailViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
); );
Widget _buildBodyColumnWrapper(LearnLessonDetailViewModel viewModel) => Widget _buildBodyColumnWrapper(LearnLessonDetailViewModel viewModel) =>
@ -173,6 +172,6 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: ()async => await _navigate(viewModel), onTap: () async => await _navigate(viewModel),
); );
} }

View File

@ -53,7 +53,7 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// rebuildUi(); // rebuildUi();
} }
Future<void> pause()async{ Future<void> pause() async {
await _chewieController?.pause(); await _chewieController?.pause();
} }
@ -67,6 +67,11 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice() async=>await _navigationService.navigateToLearnPracticeView(); Future<void> navigateToLearnPractice() async =>
await _navigationService.navigateToLearnPracticeView(
buttonLabel: 'Start Practice',
title: 'Let \'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
} }

View File

@ -45,6 +45,7 @@ class LearnLevelView extends StackedView<LearnLevelViewModel> {
Widget _buildAppBar(LearnLevelViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnLevelViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
); );
Widget _buildLevelsColumnWrapper(LearnLevelViewModel viewModel) => Widget _buildLevelsColumnWrapper(LearnLevelViewModel viewModel) =>

View File

@ -22,8 +22,17 @@ class LearnLevelViewModel extends BaseViewModel {
List<Map<String, dynamic>> get learnSubLevels => _learnSubLevels; List<Map<String, dynamic>> get learnSubLevels => _learnSubLevels;
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnModule() async => Future<void> navigateToLearnModule() async =>
_navigationService.navigateToLearnModuleView(); _navigationService.navigateToLearnModuleView();
void pop() => _navigationService.back(); Future<void> navigateToLearnPractice() async =>
await _navigationService.navigateToLearnPracticeView(
title: 'Lets Practice Level 1',
buttonLabel: 'Begin Level Practice',
subtitle:
'Lets quickly review what youve learned in this level! ',
);
} }

View File

@ -47,6 +47,7 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnModuleViewModel viewModel) => SmallAppBar(
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
); );
Widget _buildLevelsColumnWrapper(LearnModuleViewModel viewModel) => Widget _buildLevelsColumnWrapper(LearnModuleViewModel viewModel) =>

View File

@ -10,13 +10,13 @@ class LearnModuleViewModel extends BaseViewModel {
final List<Map<String, dynamic>> _modules = [ final List<Map<String, dynamic>> _modules = [
{ {
'status': ProgressStatuses.started, 'status': ProgressStatuses.completed,
'title': 'Module 1: Greetings & Introductions', 'title': 'Module 1: Greetings & Introductions',
'subtitle': 'subtitle':
'Learn how to introduce yourself, talk about your surroundings, and start simple conversations.', 'Learn how to introduce yourself, talk about your surroundings, and start simple conversations.',
}, },
{ {
'status': ProgressStatuses.pending, 'status': ProgressStatuses.started,
'title': 'Module 2: Everyday Basics', 'title': 'Module 2: Everyday Basics',
'subtitle': 'Learn numbers, colors, and common objects.', 'subtitle': 'Learn numbers, colors, and common objects.',
}, },
@ -39,4 +39,12 @@ class LearnModuleViewModel extends BaseViewModel {
Future<void> navigateToLearnLesson() async => Future<void> navigateToLearnLesson() async =>
await _navigationService.navigateToLearnLessonView(); await _navigationService.navigateToLearnLessonView();
Future<void> navigateToLearnPractice() async =>
await _navigationService.navigateToLearnPracticeView(
title: 'Lets Practice Module 1',
buttonLabel: 'Begin Module Practice',
subtitle:
'Lets quickly review what youve learned in this module! ',
);
} }

View File

@ -1,8 +1,12 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stacked/stacked.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/finish_learn_practice_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/practice_intro_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_completion_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_practice_screen.dart'; import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_result_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/listen_learn_practice_speaker_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/learn_practice_intro_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/speak_to_learn_practice_listener_screen.dart';
import 'package:yimaru_app/ui/views/learn_practice/screens/start_learn_practice_screen.dart';
import 'package:yimaru_app/ui/widgets/profile_image.dart'; import 'package:yimaru_app/ui/widgets/profile_image.dart';
import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart'; import 'package:yimaru_app/ui/widgets/speaking_partner_image.dart';
@ -13,7 +17,16 @@ import '../../widgets/small_app_bar.dart';
import 'learn_practice_viewmodel.dart'; import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> { class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
const LearnPracticeView({Key? key}) : super(key: key); final String title;
final String subtitle;
final String buttonLabel;
const LearnPracticeView(
{Key? key,
required this.title,
required this.subtitle,
required this.buttonLabel})
: super(key: key);
@override @override
LearnPracticeViewModel viewModelBuilder(BuildContext context) => LearnPracticeViewModel viewModelBuilder(BuildContext context) =>
@ -27,46 +40,59 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
) => ) =>
_buildPracticeScreensWrapper(viewModel); _buildPracticeScreensWrapper(viewModel);
Widget _buildPracticeScreensWrapper(LearnPracticeViewModel viewModel) =>
PopScope(
Widget _buildPracticeScreensWrapper(LearnPracticeViewModel viewModel) => PopScope( canPop: true,
canPop: true, onPopInvokedWithResult: (value, data) {
onPopInvokedWithResult: (value, data) { if (!value) return;
if (!value) return; WidgetsBinding.instance
WidgetsBinding.instance.addPostFrameCallback((_) => viewModel.goBack()); .addPostFrameCallback((_) => viewModel.goBack());
}, },
child: _buildScaffoldWrapper(viewModel)); child: _buildScaffoldWrapper(viewModel));
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(viewModel), body: _buildScaffoldStack(viewModel),
); );
Widget _buildScaffoldStack(LearnPracticeViewModel viewModel) => Stack(children: [
_buildBody(viewModel),
//_buildLoginWithEmailState(viewModel),
//_buildLoginWithGoogleState(viewModel)
]);
Widget _buildScaffoldStack(LearnPracticeViewModel viewModel) =>
Stack(children: [
_buildBody(viewModel),
//_buildLoginWithEmailState(viewModel),
//_buildLoginWithGoogleState(viewModel)
]);
Widget _buildBody(LearnPracticeViewModel viewModel) => Widget _buildBody(LearnPracticeViewModel viewModel) =>
IndexedStack( IndexedStack(index: viewModel.currentIndex, children: _buildScreens());
index: viewModel.currentIndex, children: _buildScreens());
List<Widget> _buildScreens() => [ List<Widget> _buildScreens() => [
_buildPracticeIntroScreen(), _buildLearnPracticeIntroScreen(),
_buildStartPracticeScreen(), _buildStartLearnPracticeScreen(),
_buildListenSpeakerScreen() _buildListenLearnPracticeSpeakerScreen(),
]; _buildSpeakToLearnPracticeListenerScreen(),
_buildFinishLearnPracticeScreen(),
_buildLearnPracticeResultScreen(),
_buildLearnPracticeCompletionScreen()
];
Widget _buildPracticeIntroScreen() => const PracticeIntroScreen(); Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen(
title: title,
subtitle: subtitle,
buttonLabel: buttonLabel,
);
Widget _buildStartPracticeScreen() => const StartPracticeScreen(); Widget _buildStartLearnPracticeScreen() => const StartLearnPracticeScreen();
Widget _buildListenSpeakerScreen() => const ListenSpeakerScreen(); Widget _buildListenLearnPracticeSpeakerScreen() =>
const ListenLearnPracticeSpeakerScreen();
Widget _buildSpeakToLearnPracticeListenerScreen() =>
const SpeakToLearnPracticeListenerScreen();
Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen();
Widget _buildLearnPracticeResultScreen() => const LearnPracticeResultScreen();
Widget _buildLearnPracticeCompletionScreen() => const LearnPracticeCompletionScreen();
} }

View File

@ -11,24 +11,30 @@ class LearnPracticeViewModel extends BaseViewModel {
int get currentIndex => _currentIndex; int get currentIndex => _currentIndex;
// Practice results
final List<Map<String, dynamic>> _practiceResults = [
{'question': 'What is your name?'},
{'question': 'Where are you from?'},
{'question': 'Where are you from?'}
];
List<Map<String, dynamic>> get practiceResults => _practiceResults;
// In-app navigation // In-app navigation
void goTo(int page) { void goTo(int page) {
_currentIndex = page; _currentIndex = page;
rebuildUi(); rebuildUi();
} }
void goBack() { void goBack() {
if(_currentIndex == 0){ if (_currentIndex == 0) {
pop(); pop();
}else{ } else {
_currentIndex--; _currentIndex--;
rebuildUi(); rebuildUi();
} }
} }
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
} }

View File

@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_svg/svg.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/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart';
class FinishLearnPracticeScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const FinishLearnPracticeScreen({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(viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [
_buildAppBarWrapper(viewModel),
_buildSpeakingIndicatorWrapper(viewModel),
_buildLowerButtonsSectionWrapper(viewModel)
];
Widget _buildAppBarWrapper(LearnPracticeViewModel viewModel) => Column(
children: [
verticalSpaceMedium,
_buildAppBar(viewModel),
],
);
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
showBackButton: false,
onTap: viewModel.goBack,
title: 'Practice Speaking',
);
Widget _buildSpeakingIndicatorWrapper(LearnPracticeViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildSpeakingIndicatorChildren(),
);
List<Widget> _buildSpeakingIndicatorChildren() => [
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
_buildSubtitle(),
];
Widget _buildIcon() => SvgPicture.asset('assets/icons/success.svg');
Widget _buildTitle() => Text(
'Practice Completed!',
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
'You sound more confident this time - great improvement!',
style: style14DG400,
textAlign: TextAlign.center,
);
Widget _buildLowerButtonsSectionWrapper(LearnPracticeViewModel viewModel) =>
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: _buildLowerButtonsSection(viewModel),
);
Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildLowerButtonsSectionChildren(viewModel),
);
List<Widget> _buildLowerButtonsSectionChildren(
LearnPracticeViewModel viewModel) =>
[
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildPracticeAgainButton(viewModel),
verticalSpaceMedium,
];
Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
foregroundColor: kcWhite,
text: 'Continue Practice',
onTap: () => viewModel.goTo(5),
backgroundColor: kcPrimaryColor,
);
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Practice Again',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: () => viewModel.goTo(1),
foregroundColor: kcPrimaryColor,
);
}

View File

@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/small_app_bar.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_elevated_button.dart';
class LearnPracticeCompletionScreen extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeCompletionScreen({super.key});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor,
body: _buildBodyWrapper(viewModel),
);
Widget _buildBodyWrapper(LearnPracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(viewModel),
);
Widget _buildBody(LearnPracticeViewModel viewModel) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(LearnPracticeViewModel viewModel) =>
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
Widget _buildUpperColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildUpperColumnChildren(viewModel),
);
List<Widget> _buildUpperColumnChildren(LearnPracticeViewModel viewModel) => [
verticalSpaceMassive,
_buildIcon(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceSmall,
_buildSubtitle(),
];
Widget _buildIcon() => SvgPicture.asset(
'assets/icons/complete.svg',
);
Widget _buildTitle() => Text(
'Yay, youve completed A1 ',
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
'Were now analyzing your speaking skills',
textAlign: TextAlign.center,
style: style14MG400,
);
Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Continue',
foregroundColor: kcWhite,
onTap: () => viewModel.pop(),
backgroundColor: kcPrimaryColor,
);
}

View File

@ -9,53 +9,61 @@ import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart'; import '../../../widgets/speaking_partner_image.dart';
class PracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> { class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
const PracticeIntroScreen({super.key}); final String title;
final String subtitle;
final String buttonLabel;
const LearnPracticeIntroScreen(
{super.key,
required this.title,
required this.subtitle,
required this.buttonLabel});
@override @override
Widget build(BuildContext context,LearnPracticeViewModel viewModel) => _buildScaffoldWrapper(viewModel); Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
_buildScaffoldWrapper(viewModel);
Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold( Widget _buildScaffoldWrapper(LearnPracticeViewModel viewModel) => Scaffold(
backgroundColor: kcBackgroundColor, backgroundColor: kcBackgroundColor,
body: _buildScaffold(viewModel), body: _buildScaffold(viewModel),
); );
Widget _buildScaffold(LearnPracticeViewModel viewModel) => Widget _buildScaffold(LearnPracticeViewModel viewModel) =>
SafeArea(child: _buildColumnWrapper(viewModel)); SafeArea(child: _buildColumnWrapper(viewModel));
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding( Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 15), padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildColumn(viewModel), child: _buildColumn(viewModel),
); );
Widget _buildColumn(LearnPracticeViewModel viewModel) => Column( Widget _buildColumn(LearnPracticeViewModel viewModel) => Column(
children: [ children: [
verticalSpaceMedium, verticalSpaceMedium,
_buildAppBar(viewModel), _buildAppBar(viewModel),
_buildBodyColumnWrapper(viewModel), _buildBodyColumnWrapper(viewModel),
], ],
); );
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
onTap: viewModel.goBack, showBackButton: true,
title: 'Practice Speaking', onTap: viewModel.goBack,
); title: 'Practice Speaking',
);
Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Expanded( Widget _buildBodyColumnWrapper(LearnPracticeViewModel viewModel) => Expanded(
child: _buildBodyColumn(viewModel), child: _buildBodyColumn(viewModel),
); );
Widget _buildBodyColumn(LearnPracticeViewModel viewModel) => Column( Widget _buildBodyColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyColumnChildren(viewModel), children: _buildBodyColumnChildren(viewModel),
); );
List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [ List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [
_buildPracticeColumnWrapper(viewModel), _buildPracticeColumnWrapper(viewModel),
_buildContinueButtonWrapper(viewModel) _buildContinueButtonWrapper(viewModel)
]; ];
Widget _buildPracticeColumnWrapper(LearnPracticeViewModel viewModel) => Widget _buildPracticeColumnWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildPracticeColumnScrollView(viewModel)); Expanded(child: _buildPracticeColumnScrollView(viewModel));
@ -66,10 +74,10 @@ class PracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
); );
Widget _buildPracticeColumn(LearnPracticeViewModel viewModel) => Column( Widget _buildPracticeColumn(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: _buildPracticeColumnChildren(viewModel), children: _buildPracticeColumnChildren(viewModel),
); );
List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) =>
[ [
@ -83,28 +91,30 @@ class PracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
_buildSubtitle() _buildSubtitle()
]; ];
Widget _buildImage() => const SpeakingPartnerImage(radius: 75,); Widget _buildImage() => const SpeakingPartnerImage(
radius: 75,
);
Widget _buildPartnerName() => Text.rich( Widget _buildPartnerName() => Text.rich(
TextSpan(text: 'Daniel', style: style14DG600, children: [ TextSpan(text: 'Daniel', style: style14DG600, children: [
TextSpan( TextSpan(
text: ' - Your Speaking Partner', text: ' - Your Speaking Partner',
style: style14MG400, style: style14MG400,
) )
]), ]),
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Let \'s practice what you just learnt!', title,
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildSubtitle() => Text( Widget _buildSubtitle() => Text(
'Ill ask you a few questions, and you can respond naturally.', subtitle,
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) => Widget _buildContinueButtonWrapper(LearnPracticeViewModel viewModel) =>
Padding( Padding(
@ -116,9 +126,9 @@ class PracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Start Practice', text: buttonLabel,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: ()=> viewModel.goTo(1), onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
); );
} }

View File

@ -0,0 +1,117 @@
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutter_svg/svg.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 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart';
import 'package:yimaru_app/ui/widgets/practice_results_wrapper.dart';
import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_column_button.dart';
import '../../../widgets/custom_elevated_button.dart';
import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultScreen({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(viewModel) => Column(
children: _buildBodyColumnChildren(viewModel),
);
List<Widget> _buildBodyColumnChildren(LearnPracticeViewModel viewModel) => [
verticalSpaceMedium,
_buildAppBar(viewModel),
verticalSpaceMedium,
_buildBodyWrapper(viewModel)
];
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
title: 'Result',
showBackButton: true,
onTap: viewModel.goBack,
);
Widget _buildBodyWrapper(LearnPracticeViewModel viewMode) => Expanded(
child: _buildBodyScroller(viewMode),
);
Widget _buildBodyScroller(LearnPracticeViewModel viewModel) =>
SingleChildScrollView(
child: _buildBody(viewModel),
);
Widget _buildBody(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(LearnPracticeViewModel viewModel) => [
_buildResultsSection(viewModel),
verticalSpaceMedium,
_buildLearnPracticeTipSection(),
verticalSpaceLarge,
_buildLowerButtonsSection(viewModel)
];
Widget _buildResultsSection(LearnPracticeViewModel viewModel) =>
PracticeResultsWrapper(data: viewModel.practiceResults);
Widget _buildLearnPracticeTipSection() => const LearnPracticeTipSection();
Widget _buildLowerButtonsSection(LearnPracticeViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildLowerButtonsSectionChildren(viewModel),
);
List<Widget> _buildLowerButtonsSectionChildren(
LearnPracticeViewModel viewModel) =>
[
_buildContinueButton(viewModel),
verticalSpaceSmall,
_buildPracticeAgainButton(viewModel),
verticalSpaceMedium,
];
Widget _buildContinueButton(LearnPracticeViewModel viewModel) => CustomElevatedButton(
height: 55,
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(6),
backgroundColor: kcPrimaryColor,
);
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: 'Retry',
borderRadius: 12,
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
onTap: () => viewModel.goTo(1),
foregroundColor: kcPrimaryColor,
);
}

View File

@ -10,8 +10,9 @@ import '../../../common/ui_helpers.dart';
import '../../../widgets/custom_column_button.dart'; import '../../../widgets/custom_column_button.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
class ListenSpeakerScreen extends ViewModelWidget<LearnPracticeViewModel> { class ListenLearnPracticeSpeakerScreen
const ListenSpeakerScreen({super.key}); extends ViewModelWidget<LearnPracticeViewModel> {
const ListenLearnPracticeSpeakerScreen({super.key});
Future<void> _showSheet( Future<void> _showSheet(
{required BuildContext context, {required BuildContext context,
@ -86,6 +87,7 @@ class ListenSpeakerScreen extends ViewModelWidget<LearnPracticeViewModel> {
); );
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
showBackButton: true,
onTap: viewModel.goBack, onTap: viewModel.goBack,
title: 'Practice Speaking', title: 'Practice Speaking',
); );
@ -190,7 +192,7 @@ class ListenSpeakerScreen extends ViewModelWidget<LearnPracticeViewModel> {
required LearnPracticeViewModel viewModel}) => required LearnPracticeViewModel viewModel}) =>
[ [
_buildReplyButtonWrapper(), _buildReplyButtonWrapper(),
_buildMicButtonWrapper(), _buildMicButtonWrapper(viewModel),
_buildCancelButtonWrapper(context: context, viewModel: viewModel) _buildCancelButtonWrapper(context: context, viewModel: viewModel)
]; ];
@ -199,10 +201,11 @@ class ListenSpeakerScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildReplyButton() => const CustomColumnButton( Widget _buildReplyButton() => const CustomColumnButton(
icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); icon: Icons.replay, label: 'Reply', color: kcPrimaryColor);
Widget _buildMicButtonWrapper() => Expanded(child: _buildMicButton()); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildMicButton(viewModel));
Widget _buildMicButton() => ElevatedButton( Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton(
onPressed: () {}, onPressed: () => viewModel.goTo(3),
style: const ButtonStyle( style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()), shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(15)), padding: WidgetStatePropertyAll(EdgeInsets.all(15)),

View File

@ -0,0 +1,232 @@
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 SpeakToLearnPracticeListenerScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const SpeakToLearnPracticeListenerScreen({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,
showBackButton: true,
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(
'You are speaking...',
style: style14P400,
textAlign: TextAlign.center,
);
Widget _buildSpeakingIndicator() => Container(
height: 100,
alignment: Alignment.center,
child: _buildSpinner(),
);
Widget _buildSpinner() => const SpinKitWave(
size: 75,
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}) =>
[
_buildEmptySpace(),
_buildCheckButtonWrapper(viewModel),
_buildCancelButtonWrapper(context: context, viewModel: viewModel)
];
Widget _buildEmptySpace() => Expanded(child: Container());
Widget _buildCheckButtonWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildCheckButton(viewModel));
Widget _buildCheckButton(LearnPracticeViewModel viewModel) => ElevatedButton(
onPressed: () => viewModel.goTo(4),
style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(15)),
shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
),
child: _buildCheckIcon(),
);
Widget _buildCheckIcon() => const Icon(
Icons.check,
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);
}

View File

@ -7,8 +7,8 @@ import '../../../common/app_colors.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/small_app_bar.dart'; import '../../../widgets/small_app_bar.dart';
class StartPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> { class StartLearnPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
const StartPracticeScreen({super.key}); const StartLearnPracticeScreen({super.key});
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
@ -47,6 +47,7 @@ class StartPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar( Widget _buildAppBar(LearnPracticeViewModel viewModel) => SmallAppBar(
onTap: viewModel.goBack, onTap: viewModel.goBack,
showBackButton: true,
title: 'Practice Speaking', title: 'Practice Speaking',
); );
@ -122,7 +123,7 @@ class StartPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
[ [
_buildActionLabel(), _buildActionLabel(),
verticalSpaceMedium, verticalSpaceMedium,
_buildButtonsRowWrapper(), _buildButtonsRowWrapper(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
]; ];
@ -132,15 +133,15 @@ class StartPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
); );
Widget _buildButtonsRowWrapper() => Row( Widget _buildButtonsRowWrapper(LearnPracticeViewModel viewModel) => Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildButtonsRowChildren(), children: _buildButtonsRowChildren(viewModel),
); );
List<Widget> _buildButtonsRowChildren() => [ List<Widget> _buildButtonsRowChildren(LearnPracticeViewModel viewModel) => [
_buildReplyButtonWrapper(), _buildReplyButtonWrapper(),
_buildMicButtonWrapper(), _buildMicButtonWrapper(viewModel),
_buildEmptySpace() _buildEmptySpace()
]; ];
@ -149,10 +150,11 @@ class StartPracticeScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildReplyButton() => const CustomColumnButton( Widget _buildReplyButton() => const CustomColumnButton(
icon: Icons.replay, label: 'Reply', color: kcPrimaryColor); icon: Icons.replay, label: 'Reply', color: kcPrimaryColor);
Widget _buildMicButtonWrapper() => Expanded(child: _buildMicButton()); Widget _buildMicButtonWrapper(LearnPracticeViewModel viewModel) =>
Expanded(child: _buildMicButton(viewModel));
Widget _buildMicButton() => ElevatedButton( Widget _buildMicButton(LearnPracticeViewModel viewModel) => ElevatedButton(
onPressed: () {}, onPressed: () => viewModel.goTo(2),
style: const ButtonStyle( style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()), shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(15)), padding: WidgetStatePropertyAll(EdgeInsets.all(15)),

View File

@ -56,10 +56,6 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
}, },
child: _buildBody(viewModel)); child: _buildBody(viewModel));
Widget _buildBody(LoginViewModel viewModel) => Widget _buildBody(LoginViewModel viewModel) =>
IndexedStack(index: viewModel.currentIndex, children: _buildScreens()); IndexedStack(index: viewModel.currentIndex, children: _buildScreens());
@ -78,5 +74,4 @@ class LoginView extends StackedView<LoginViewModel> with $LoginView {
Widget _buildLoginOtpScreen() => LoginOtpScreen( Widget _buildLoginOtpScreen() => LoginOtpScreen(
otpController: otpController, otpController: otpController,
phoneNumberController: phoneNumberController); phoneNumberController: phoneNumberController);
} }

View File

@ -142,7 +142,7 @@ class LoginViewModel extends FormViewModel {
await _navigationService.navigateToForgetPasswordView(); await _navigationService.navigateToForgetPasswordView();
Future<void> replaceWithHome() async => Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView()); await _navigationService.clearStackAndShow(Routes.homeView);
// Remote api calls // Remote api calls
@ -171,8 +171,9 @@ class LoginViewModel extends FormViewModel {
} }
} }
Future<void> signInWithGoogle() async => await runBusyFuture(_signInWithGoogle(), Future<void> signInWithGoogle() async =>
busyObject: StateObjects.loginWithGoogle); await runBusyFuture(_signInWithGoogle(),
busyObject: StateObjects.loginWithGoogle);
Future<void> _signInWithGoogle() async { Future<void> _signInWithGoogle() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {

View File

@ -21,68 +21,83 @@ class LoginOtpScreen extends ViewModelWidget<LoginViewModel> {
required this.otpController, required this.otpController,
required this.phoneNumberController}); required this.phoneNumberController});
Widget getPadding(context){ Widget getPadding(context) {
double half = screenHeight(context)/2; double half = screenHeight(context) / 2;
return SizedBox(height: half + 325 - half,); return SizedBox(
height: half + 325 - half,
);
} }
@override @override
Widget build(BuildContext context, LoginViewModel viewModel) => Widget build(BuildContext context, LoginViewModel viewModel) =>
_buildScaffoldWrapper(context: context,viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( Widget _buildScaffoldWrapper(
backgroundColor: kcBackgroundColor, {required BuildContext context, required LoginViewModel viewModel}) =>
body: _buildScaffold(context: context,viewModel: 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( Widget _buildScaffold(
padding: const EdgeInsets.symmetric(horizontal: 15), {required BuildContext context, required LoginViewModel viewModel}) =>
child: _buildBody(context: context,viewModel: viewModel), Column(
); crossAxisAlignment: CrossAxisAlignment.start,
children:
_buildScaffoldChildren(context: context, viewModel: viewModel),
);
Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( List<Widget> _buildScaffoldChildren(
crossAxisAlignment: CrossAxisAlignment.start, {required BuildContext context, required LoginViewModel viewModel}) =>
mainAxisAlignment: MainAxisAlignment.spaceBetween, [
children: _buildBodyChildren(context: context,viewModel: 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),
);
List<Widget> _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => Widget _buildBodyWrapper(
[_buildUpperColumn(viewModel),getPadding(context), _buildContinueButton(viewModel)]; {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(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyChildren(
{required BuildContext context, required LoginViewModel viewModel}) =>
[
_buildUpperColumn(viewModel),
getPadding(context),
_buildContinueButton(viewModel)
];
Widget _buildUpperColumn(LoginViewModel viewModel) => Column( Widget _buildUpperColumn(LoginViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,

View File

@ -22,10 +22,11 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
required this.emailController, required this.emailController,
required this.passwordController}); required this.passwordController});
Widget getPadding(context) {
Widget getPadding(context){ double half = screenHeight(context) / 2;
double half = screenHeight(context)/2; return SizedBox(
return SizedBox(height: half + 25 - half,); height: half + 25 - half,
);
} }
Future<void> _login(LoginViewModel viewModel) async { Future<void> _login(LoginViewModel viewModel) async {
@ -42,57 +43,75 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
@override @override
Widget build(BuildContext context, LoginViewModel viewModel) => Widget build(BuildContext context, LoginViewModel viewModel) =>
_buildScaffoldWrapper(context: context,viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( Widget _buildScaffoldWrapper(
backgroundColor: kcBackgroundColor, {required BuildContext context, required LoginViewModel viewModel}) =>
body: _buildScaffoldStack(context: context,viewModel: viewModel), Scaffold(
); backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack({required BuildContext context,required LoginViewModel viewModel}) => Stack(children: [ Widget _buildScaffoldStack(
_buildScaffold(context: context,viewModel: viewModel), {required BuildContext context, required LoginViewModel viewModel}) =>
_buildLoginWithEmailState(viewModel), Stack(children: [
_buildLoginWithGoogleState(viewModel) _buildScaffold(context: context, viewModel: viewModel),
]); _buildLoginWithEmailState(viewModel),
_buildLoginWithGoogleState(viewModel)
]);
Widget _buildScaffold({required BuildContext context,required LoginViewModel viewModel}) => Column( Widget _buildScaffold(
crossAxisAlignment: CrossAxisAlignment.start, {required BuildContext context, required LoginViewModel viewModel}) =>
children: _buildScaffoldChildren(context: context,viewModel: viewModel), Column(
); crossAxisAlignment: CrossAxisAlignment.start,
children:
_buildScaffoldChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildScaffoldChildren({required BuildContext context,required LoginViewModel viewModel}) => List<Widget> _buildScaffoldChildren(
[_buildAppBar(viewModel), _buildExpandedBody(context: context,viewModel: viewModel)]; {required BuildContext context, required LoginViewModel viewModel}) =>
[
_buildAppBar(viewModel),
_buildExpandedBody(context: context, viewModel: viewModel)
];
Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar( Widget _buildAppBar(LoginViewModel viewModel) => const LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
); );
Widget _buildExpandedBody({required BuildContext context,required LoginViewModel viewModel}) => Widget _buildExpandedBody(
Expanded(child: _buildColumnScroller(context: context,viewModel: viewModel)); {required BuildContext context, required LoginViewModel viewModel}) =>
Expanded(
child: _buildColumnScroller(context: context, viewModel: viewModel));
Widget _buildColumnScroller({required BuildContext context,required LoginViewModel viewModel}) => Widget _buildColumnScroller(
{required BuildContext context, required LoginViewModel viewModel}) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(context: context,viewModel: viewModel), child: _buildBodyWrapper(context: context, viewModel: viewModel),
); );
Widget _buildBodyWrapper({required BuildContext context,required LoginViewModel viewModel}) => Padding( Widget _buildBodyWrapper(
padding: const EdgeInsets.symmetric(horizontal: 15), {required BuildContext context, required LoginViewModel viewModel}) =>
child: _buildBody(context: context,viewModel: viewModel), Padding(
); padding: const EdgeInsets.symmetric(horizontal: 15),
child: _buildBody(context: context, viewModel: viewModel),
);
Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( Widget _buildBody(
{required BuildContext context, required LoginViewModel viewModel}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildBodyChildren(context: context,viewModel: viewModel), children: _buildBodyChildren(context: context, viewModel: viewModel),
); );
List<Widget> _buildBodyChildren(
{required BuildContext context, required LoginViewModel viewModel}) =>
[
List<Widget> _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => _buildUpperColumn(viewModel),
[_buildUpperColumn(viewModel),getPadding(context), _buildLowerColumn(viewModel)]; getPadding(context),
_buildLowerColumn(viewModel)
];
Widget _buildUpperColumn(LoginViewModel viewModel) => Column( Widget _buildUpperColumn(LoginViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -246,7 +265,6 @@ class LoginWithEmailScreen extends ViewModelWidget<LoginViewModel> {
text: 'Login with Phone Number', text: 'Login with Phone Number',
); );
Widget _buildLoginWithEmailState(LoginViewModel viewModel) => Widget _buildLoginWithEmailState(LoginViewModel viewModel) =>
viewModel.busy(StateObjects.loginWithEmail) viewModel.busy(StateObjects.loginWithEmail)
? const PageLoadingIndicator() ? const PageLoadingIndicator()

View File

@ -18,70 +18,82 @@ class LoginWithPhoneNumberScreen extends ViewModelWidget<LoginViewModel> {
const LoginWithPhoneNumberScreen( const LoginWithPhoneNumberScreen(
{super.key, required this.phoneNumberController}); {super.key, required this.phoneNumberController});
Widget getPadding(context){ Widget getPadding(context) {
double half = screenHeight(context)/2; double half = screenHeight(context) / 2;
return SizedBox(height: half + 175 - half,); return SizedBox(
height: half + 175 - half,
);
} }
@override @override
Widget build(BuildContext context, LoginViewModel viewModel) => Widget build(BuildContext context, LoginViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel);
_buildScaffoldWrapper(context: context,viewModel: viewModel); Widget _buildScaffoldWrapper(
{required BuildContext context, required LoginViewModel viewModel}) =>
Scaffold(
backgroundColor: kcBackgroundColor,
Widget _buildScaffoldWrapper({required BuildContext context,required LoginViewModel viewModel}) => Scaffold( body: _buildScaffold(context: context, viewModel: viewModel),
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( Widget _buildScaffold(
padding: const EdgeInsets.symmetric(horizontal: 15), {required BuildContext context, required LoginViewModel viewModel}) =>
child: _buildBody(context: context,viewModel: viewModel), Column(
); crossAxisAlignment: CrossAxisAlignment.start,
children:
_buildScaffoldChildren(context: context, viewModel: viewModel),
);
Widget _buildBody({required BuildContext context,required LoginViewModel viewModel}) => Column( List<Widget> _buildScaffoldChildren(
crossAxisAlignment: CrossAxisAlignment.start, {required BuildContext context, required LoginViewModel viewModel}) =>
children: _buildBodyChildren(context: context,viewModel: 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),
);
List<Widget> _buildBodyChildren({required BuildContext context,required LoginViewModel viewModel}) => Widget _buildBodyWrapper(
[_buildUpperColumn(viewModel),getPadding(context), _buildLowerColumn(viewModel)]; {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( Widget _buildUpperColumn(LoginViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: _buildUpperColumnChildren(viewModel), children: _buildUpperColumnChildren(viewModel),
); );
List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [ List<Widget> _buildUpperColumnChildren(LoginViewModel viewModel) => [
verticalSpaceMedium, verticalSpaceMedium,

View File

@ -481,5 +481,5 @@ class OnboardingViewModel extends FormViewModel {
await _navigationService.navigateToAssessmentView(data: _userData); await _navigationService.navigateToAssessmentView(data: _userData);
Future<void> replaceWithHome() async => Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView()); await _navigationService.clearStackAndShow(Routes.homeView);
} }

View File

@ -58,6 +58,7 @@ class OngoingProgressView extends StackedView<OngoingProgressViewModel> {
Widget _buildAppbar(OngoingProgressViewModel viewModel) => SmallAppBar( Widget _buildAppbar(OngoingProgressViewModel viewModel) => SmallAppBar(
title: 'My Progress', title: 'My Progress',
showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
); );

View File

@ -54,8 +54,9 @@ class PrivacyPolicyView extends StackedView<PrivacyPolicyViewModel> {
); );
Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar( Widget _buildAppbar(PrivacyPolicyViewModel viewModel) => SmallAppBar(
title: 'Privacy Policy', showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
title: 'Privacy Policy',
); );
Widget _buildContentWrapper(PrivacyPolicyViewModel viewModel) => Widget _buildContentWrapper(PrivacyPolicyViewModel viewModel) =>

View File

@ -42,7 +42,7 @@ class ProfileViewModel extends ReactiveViewModel {
pop(); pop();
if (image != null) { if (image != null) {
await updateProfilePicture(image); await updateProfilePicture(image);
await _authenticationService.saveProfileImage(image); await _authenticationService.saveProfilePicture(image);
} }
} }
@ -54,7 +54,7 @@ class ProfileViewModel extends ReactiveViewModel {
pop(); pop();
if (image != null) { if (image != null) {
await updateProfilePicture(image); await updateProfilePicture(image);
await _authenticationService.saveProfileImage(image); await _authenticationService.saveProfilePicture(image);
} }
} }

View File

@ -148,8 +148,9 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
]; ];
Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar( Widget _buildAppbar(ProfileDetailViewModel viewModel) => SmallAppBar(
title: 'Edit Profile',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
title: 'Edit Profile',
); );
Widget _buildColumnWrapper( Widget _buildColumnWrapper(

View File

@ -185,7 +185,7 @@ class ProfileDetailViewModel extends ReactiveViewModel
pop(); pop();
if (image != null) { if (image != null) {
await updateProfilePicture(image); await updateProfilePicture(image);
await _authenticationService.saveProfileImage(image); await _authenticationService.saveProfilePicture(image);
} }
} }
@ -197,7 +197,7 @@ class ProfileDetailViewModel extends ReactiveViewModel
pop(); pop();
if (image != null) { if (image != null) {
await updateProfilePicture(image); await updateProfilePicture(image);
await _authenticationService.saveProfileImage(image); await _authenticationService.saveProfilePicture(image);
} }
} }

View File

@ -57,6 +57,7 @@ class ProgressView extends StackedView<ProgressViewModel> {
Widget _buildAppbar(ProgressViewModel viewModel) => SmallAppBar( Widget _buildAppbar(ProgressViewModel viewModel) => SmallAppBar(
title: 'My Progress', title: 'My Progress',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
); );
Widget _buildContentWrapper(ProgressViewModel viewModel) => Widget _buildContentWrapper(ProgressViewModel viewModel) =>

View File

@ -87,8 +87,6 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
_pop(value: value, viewModel: viewModel), _pop(value: value, viewModel: viewModel),
child: _buildBody(viewModel)); child: _buildBody(viewModel));
Widget _buildBody(RegisterViewModel viewModel) => Widget _buildBody(RegisterViewModel viewModel) =>
IndexedStack(index: viewModel.currentPage, children: _buildScreens()); IndexedStack(index: viewModel.currentPage, children: _buildScreens());
@ -114,5 +112,4 @@ class RegisterView extends StackedView<RegisterViewModel> with $RegisterView {
Widget _buildCreatePasswordScreen() => CreatePasswordScreen( Widget _buildCreatePasswordScreen() => CreatePasswordScreen(
passwordController: passwordController, passwordController: passwordController,
confirmPasswordController: confirmPasswordController); confirmPasswordController: confirmPasswordController);
} }

View File

@ -23,7 +23,6 @@ class RegisterViewModel extends FormViewModel {
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _authenticationService = locator<AuthenticationService>(); final _authenticationService = locator<AuthenticationService>();
// Navigation // Navigation
@ -284,17 +283,18 @@ class RegisterViewModel extends FormViewModel {
await _navigationService.replaceWithLoginView(); await _navigationService.replaceWithLoginView();
Future<void> replaceWithHome() async => Future<void> replaceWithHome() async =>
await _navigationService.clearStackAndShowView(const HomeView()); await _navigationService.clearStackAndShow(Routes.homeView);
// Remote api calls // Remote api calls
// Register // Register
Future<void> registerWithEmail() async => Future<void> registerWithEmail() async => await runBusyFuture(_register(),
await runBusyFuture(_register(), busyObject: StateObjects.registerWithEmail); busyObject: StateObjects.registerWithEmail);
Future<void> _register() async { Future<void> _register() async {
if (await _statusChecker.checkConnection()) { if (await _statusChecker.checkConnection()) {
Map<String, dynamic> response = await _apiService.registerWithEmail(_userData); Map<String, dynamic> response =
await _apiService.registerWithEmail(_userData);
if (response['status'] == ResponseStatus.success) { if (response['status'] == ResponseStatus.success) {
goTo(page: 3); goTo(page: 3);

View File

@ -231,8 +231,6 @@ class RegisterWithEmailScreen extends ViewModelWidget<RegisterViewModel> {
onTap: () => viewModel.goTo(page: 1), onTap: () => viewModel.goTo(page: 1),
); );
Widget _buildRegisterWithGoogleState(RegisterViewModel viewModel) => Widget _buildRegisterWithGoogleState(RegisterViewModel viewModel) =>
viewModel.busy(StateObjects.registerWithEmail) viewModel.busy(StateObjects.registerWithEmail)
? const PageLoadingIndicator() ? const PageLoadingIndicator()

View File

@ -26,10 +26,11 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
required this.emailController, required this.emailController,
required this.phoneNumberController}); required this.phoneNumberController});
Widget getPadding(context) {
Widget getPadding(context){ double half = screenHeight(context) / 2;
double half = screenHeight(context)/2; return SizedBox(
return SizedBox(height: half + 325 - half,); height: half + 325 - half,
);
} }
Future<void> _verifyOtp(RegisterViewModel viewModel) async { Future<void> _verifyOtp(RegisterViewModel viewModel) async {
@ -45,20 +46,21 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
await viewModel.verifyOtp(); await viewModel.verifyOtp();
} }
@override @override
Widget build(BuildContext context, RegisterViewModel viewModel) => Widget build(BuildContext context, RegisterViewModel viewModel) =>
_buildScaffoldWrapper(context: context, viewModel: viewModel); _buildScaffoldWrapper(context: context, viewModel: viewModel);
Widget _buildScaffoldWrapper( {required BuildContext context, Widget _buildScaffoldWrapper(
required RegisterViewModel viewModel}) => Scaffold( {required BuildContext context,
backgroundColor: kcBackgroundColor, required RegisterViewModel viewModel}) =>
body: _buildScaffoldStack(context: context, viewModel: viewModel), Scaffold(
); backgroundColor: kcBackgroundColor,
body: _buildScaffoldStack(context: context, viewModel: viewModel),
);
Widget _buildScaffoldStack( Widget _buildScaffoldStack(
{required BuildContext context, {required BuildContext context,
required RegisterViewModel viewModel}) => required RegisterViewModel viewModel}) =>
Stack( Stack(
children: [ children: [
_buildScaffold(context: context, viewModel: viewModel), _buildScaffold(context: context, viewModel: viewModel),
@ -66,49 +68,66 @@ class RegistrationOtpScreen extends ViewModelWidget<RegisterViewModel> {
], ],
); );
Widget _buildScaffold( {required BuildContext context, Widget _buildScaffold(
required RegisterViewModel viewModel}) => Column( {required BuildContext context,
crossAxisAlignment: CrossAxisAlignment.start, required RegisterViewModel viewModel}) =>
children: _buildScaffoldChildren(context: context, viewModel: viewModel), Column(
); crossAxisAlignment: CrossAxisAlignment.start,
children:
_buildScaffoldChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildScaffoldChildren( {required BuildContext context, List<Widget> _buildScaffoldChildren(
required RegisterViewModel viewModel}) => {required BuildContext context,
[_buildAppBar(viewModel), _buildExpandedBody(context: context, viewModel: viewModel)]; required RegisterViewModel viewModel}) =>
[
_buildAppBar(viewModel),
_buildExpandedBody(context: context, viewModel: viewModel)
];
Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar( Widget _buildAppBar(RegisterViewModel viewModel) => const LargeAppBar(
showBackButton: false, showBackButton: false,
showLanguageSelection: true, showLanguageSelection: true,
); );
Widget _buildExpandedBody( {required BuildContext context, Widget _buildExpandedBody(
required RegisterViewModel viewModel}) => {required BuildContext context,
Expanded(child: _buildColumnScroller(context: context, viewModel: viewModel)); required RegisterViewModel viewModel}) =>
Expanded(
child: _buildColumnScroller(context: context, viewModel: viewModel));
Widget _buildColumnScroller( {required BuildContext context, Widget _buildColumnScroller(
required RegisterViewModel viewModel}) => {required BuildContext context,
required RegisterViewModel viewModel}) =>
SingleChildScrollView( SingleChildScrollView(
child: _buildBodyWrapper(context: context, viewModel: viewModel), child: _buildBodyWrapper(context: context, viewModel: viewModel),
); );
Widget _buildBodyWrapper( {required BuildContext context, Widget _buildBodyWrapper(
required RegisterViewModel viewModel}) => Padding( {required BuildContext context,
padding: const EdgeInsets.symmetric(horizontal: 15), required RegisterViewModel viewModel}) =>
child: _buildBody(context: context, viewModel: 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(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyChildren( {required BuildContext context,
required RegisterViewModel viewModel}) =>
[_buildUpperColumn(viewModel),getPadding(context), _buildContinueButtonWrapper(viewModel)];
Widget _buildBody(
{required BuildContext context,
required RegisterViewModel viewModel}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildBodyChildren(context: context, viewModel: viewModel),
);
List<Widget> _buildBodyChildren(
{required BuildContext context,
required RegisterViewModel viewModel}) =>
[
_buildUpperColumn(viewModel),
getPadding(context),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildUpperColumn(RegisterViewModel viewModel) => Column( Widget _buildUpperColumn(RegisterViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,

View File

@ -70,8 +70,7 @@ class StartupView extends StackedView<StartupViewModel> {
_buildIndicatorWrapper(), _buildIndicatorWrapper(),
]; ];
Widget _buildLoadingText() => Widget _buildLoadingText() => Text('$label ...', style: style16W600);
Text('$label ...', style: const TextStyle(color: kcWhite, fontSize: 16));
Widget _buildIndicatorWrapper() => SizedBox( Widget _buildIndicatorWrapper() => SizedBox(
width: 16, width: 16,
@ -83,7 +82,7 @@ class StartupView extends StackedView<StartupViewModel> {
const CustomCircularProgressIndicator(color: kcWhite); const CustomCircularProgressIndicator(color: kcWhite);
Widget _buildIconWrapper() => Padding( Widget _buildIconWrapper() => Padding(
padding: const EdgeInsets.only(top: 100), padding: const EdgeInsets.only(top: 120),
child: _buildIcon(), child: _buildIcon(),
); );

View File

@ -52,6 +52,7 @@ class SupportView extends StackedView<SupportViewModel> {
Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(SupportViewModel viewModel) => SmallAppBar(
title: 'Need Help?', title: 'Need Help?',
showBackButton: true,
onTap: viewModel.pop, onTap: viewModel.pop,
); );

View File

@ -49,8 +49,9 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
); );
Widget _buildAppbar(TelegramSupportViewModel viewModel) => SmallAppBar( Widget _buildAppbar(TelegramSupportViewModel viewModel) => SmallAppBar(
title: 'Telegram Support',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
title: 'Telegram Support',
); );
Widget _buildExpandedColumn(TelegramSupportViewModel viewModel) => Widget _buildExpandedColumn(TelegramSupportViewModel viewModel) =>

View File

@ -57,8 +57,9 @@ class TermsAndConditionsView extends StackedView<TermsAndConditionsViewModel> {
); );
Widget _buildAppbar(TermsAndConditionsViewModel viewModel) => SmallAppBar( Widget _buildAppbar(TermsAndConditionsViewModel viewModel) => SmallAppBar(
title: 'Terms and Conditions',
onTap: viewModel.pop, onTap: viewModel.pop,
showBackButton: true,
title: 'Terms and Conditions',
); );
Widget _buildContentWrapper(TermsAndConditionsViewModel viewModel) => Widget _buildContentWrapper(TermsAndConditionsViewModel viewModel) =>

View File

@ -59,14 +59,10 @@ class FirstWelcomeScreen extends ViewModelWidget<WelcomeViewModel> {
height: 50, height: 50,
); );
Widget _buildTitle() => const Text( Widget _buildTitle() => Text(
'Small daily practice. Big lifelong results.', 'Small daily practice. Big lifelong results.',
style: style25W600,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(
fontSize: 30,
color: kcWhite,
fontWeight: FontWeight.w600,
),
); );
Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align( Widget _buildContinueButtonWrapper(WelcomeViewModel viewModel) => Align(
@ -85,8 +81,8 @@ class FirstWelcomeScreen extends ViewModelWidget<WelcomeViewModel> {
borderRadius: 12, borderRadius: 12,
text: 'Start Learning', text: 'Start Learning',
backgroundColor: kcWhite, backgroundColor: kcWhite,
trailingIcon: Icons.arrow_forward,
onTap: () => viewModel.next(), onTap: () => viewModel.next(),
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
trailingIcon: Icons.arrow_forward,
); );
} }

View File

@ -37,8 +37,7 @@ class CancelLearnPracticeSheet extends StatelessWidget {
verticalSpaceLarge, verticalSpaceLarge,
_buildContinueButton(), _buildContinueButton(),
_buildEndButton(), _buildEndButton(),
];
];
Widget _buildImage() => const SpeakingPartnerImage( Widget _buildImage() => const SpeakingPartnerImage(
radius: 45, radius: 45,

View File

@ -9,6 +9,6 @@ class CustomCircularProgressIndicator extends StatelessWidget {
Widget _buildIndicator() => CircularProgressIndicator( Widget _buildIndicator() => CircularProgressIndicator(
color: color, color: color,
strokeWidth: 6, strokeWidth: 5,
); );
} }

View File

@ -29,22 +29,25 @@ class CustomColumnButton extends StatelessWidget {
children: _buildColumnChildren(), children: _buildColumnChildren(),
); );
List<Widget> _buildColumnChildren() => [ List<Widget> _buildColumnChildren() => [_buildIconWrapper(), _buildLabel()];
_buildIconWrapper(),
_buildLabel()
];
Widget _buildIconWrapper() => Container( Widget _buildIconWrapper() => Container(
padding:const EdgeInsets.all(5), padding: const EdgeInsets.all(5),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: color.withOpacity(0.1), color: color.withOpacity(0.1),
border: Border.all(color: color.withOpacity(0.75)) border: Border.all(color: color.withOpacity(0.75))),
), child: _buildIcon(),
child: _buildIcon(), );
);
Widget _buildLabel()=> Text(label,style: style14LG400.copyWith(color: color),); Widget _buildLabel() => Text(
label,
style: style14LG400.copyWith(color: color),
);
Widget _buildIcon()=> Icon(icon,size: 14,color: color,); Widget _buildIcon() => Icon(
icon,
size: 14,
color: color,
);
} }

View File

@ -57,10 +57,7 @@ class CustomListTile extends StatelessWidget {
Widget _buildTrailingText() => Text( Widget _buildTrailingText() => Text(
language ?? '', language ?? '',
style: const TextStyle( style: style12DG400,
fontSize: 12,
color: kcDarkGrey,
),
); );
Widget _buildTrailingIcon() => const Icon( Widget _buildTrailingIcon() => const Icon(

View File

@ -13,10 +13,12 @@ class LearnLessonTile extends StatelessWidget {
final String thumbnail; final String thumbnail;
final ProgressStatuses status; final ProgressStatuses status;
final GestureTapCallback? onLessonTap; final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
const LearnLessonTile({ const LearnLessonTile({
super.key, super.key,
this.onLessonTap, this.onLessonTap,
this.onPracticeTap,
required this.title, required this.title,
required this.status, required this.status,
required this.thumbnail, required this.thumbnail,
@ -181,11 +183,12 @@ class LearnLessonTile extends StatelessWidget {
_buildLessonButton(), _buildLessonButton(),
]; ];
Widget _buildPracticeButton() => const CustomElevatedButton( Widget _buildPracticeButton() => CustomElevatedButton(
height: 15, height: 15,
width: 135, width: 135,
text: 'Practice', text: 'Practice',
borderRadius: 12, borderRadius: 12,
onTap: onPracticeTap,
trailingIcon: Icons.mic, trailingIcon: Icons.mic,
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,

View File

@ -218,8 +218,9 @@ class LearnModuleTile extends ViewModelWidget<LearnModuleViewModel> {
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () async => onTap: () async => status == ProgressStatuses.completed
await _showSheet(context: context, viewModel: viewModel), ? await viewModel.navigateToLearnPractice()
: await _showSheet(context: context, viewModel: viewModel),
); );
Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet( Widget _buildSheet(LearnModuleViewModel viewModel) => FinishPracticeSheet(

View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/custom_list_tile.dart';
class LearnPracticeTipSection extends StatelessWidget {
const LearnPracticeTipSection({super.key});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: kcBlue.withOpacity(0.1),
),
child: _buildColumn(),
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[_buildTitleWrapper(), verticalSpaceTiny, _buildContent()];
Widget _buildTitleWrapper() =>
Row(
children: [
_buildLeading(),horizontalSpaceSmall,_buildTitle()
],
);
Widget _buildLeading() => const Icon(
Icons.lightbulb_outline_rounded,
color: kcBlue,
);
Widget _buildTitle() => Text(
'Quick Tip',
style: style16B600,
);
Widget _buildContent() => Text(
"You can always do better!\nSpeak in full sentences instead of short phrases.\nMaintain a steady pace, not too fast, not too slow.\nUse varied vocabulary to make your answers richer.\nPause naturally instead of using fillers like “um” or “uh”.",
style: style14B400,
textAlign: TextAlign.start,
);
}

View File

@ -123,14 +123,14 @@ class LearnSubLevelTile extends ViewModelWidget<LearnLevelViewModel> {
); );
Widget _buildPracticeButton(LearnLevelViewModel viewModel) => Widget _buildPracticeButton(LearnLevelViewModel viewModel) =>
const CustomElevatedButton( CustomElevatedButton(
height: 15, height: 15,
text: 'Practice', text: 'Practice',
borderRadius: 12, borderRadius: 12,
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () async => await viewModel.navigateToLearnPractice()
// onTap: () async => await viewModel.navigateToLearnLevel(), // onTap: () async => await viewModel.navigateToLearnLevel(),
); );
} }

View File

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
class PracticeResponseCard extends StatelessWidget {
final String title;
final String subtitle;
const PracticeResponseCard(
{super.key, required this.title, required this.subtitle});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
decoration: BoxDecoration(
color: kcWhite,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
child: _buildRow(),
);
Widget _buildRow() => Row(
children: [
_buildPlayButton(),
_buildColumnWrapper()
],
);
Widget _buildPlayButton() => ElevatedButton(
onPressed: () {},
style: const ButtonStyle(
shape: WidgetStatePropertyAll(CircleBorder()),
padding: WidgetStatePropertyAll(EdgeInsets.all(5)),
shadowColor: WidgetStatePropertyAll(kcPrimaryColor),
backgroundColor: WidgetStatePropertyAll(kcPrimaryColor),
),
child: _buildPlayIcon(),
);
Widget _buildPlayIcon() => const Icon(
Icons.play_arrow_rounded,
size: 25,
color: kcWhite,
);
Widget _buildColumnWrapper() => Expanded(child: _buildColumn());
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [_buildTitle(), _buildSubtitle()];
Widget _buildTitle() => Text(
title,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
Widget _buildSubtitle() => Text(
subtitle,
maxLines: 1,
softWrap: false,
style: style12RP600,
);
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/practice_response_card.dart';
class PracticeResultCard extends StatelessWidget {
final int index;
final Map<String, dynamic> data;
const PracticeResultCard(
{super.key, required this.index, required this.data});
@override
Widget build(BuildContext context) => _buildColumnWrapper();
Widget _buildColumnWrapper() => SizedBox(height: 100,width: double.maxFinite,child: _buildColumn(),);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() => [_buildQuestion(),verticalSpaceSmall, _buildRow()];
Widget _buildQuestion() => Text(
'$index. ${data['question']}',
style: style14DG400,
);
Widget _buildRow() => Row(
children: _buildRowChildren(),
);
List<Widget> _buildRowChildren() =>
[_buildSampleResponseWrapper(),horizontalSpaceSmall, _buildActualResponseWrapper()];
Widget _buildSampleResponseWrapper() =>
Expanded(child: _buildSampleResponse());
Widget _buildSampleResponse() =>
const PracticeResponseCard(title: 'Sample Answer', subtitle: '0:54');
Widget _buildActualResponseWrapper() =>
Expanded(child: _buildActualResponse());
Widget _buildActualResponse() =>
const PracticeResponseCard(title: 'Sample Answer', subtitle: '0:54');
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/ui_helpers.dart';
import 'package:yimaru_app/ui/widgets/practice_result_card.dart';
import '../common/app_colors.dart';
class PracticeResultsWrapper extends StatelessWidget {
final List<Map<String, dynamic>> data;
const PracticeResultsWrapper({super.key, required this.data});
@override
Widget build(BuildContext context) => _buildContainer();
Widget _buildContainer() => Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 25),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: kcPrimaryColor.withOpacity(0.1),
),
child: _buildColumn(),
);
Widget _buildColumn() => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildColumnChildren(),
);
List<Widget> _buildColumnChildren() =>
[_buildTitle(), verticalSpaceSmall, _buildResults()];
Widget _buildTitle() => Text(
'Conversation Review',
style: style16DG600,
textAlign: TextAlign.center,
);
Widget _buildResults() => ListView.separated(
shrinkWrap: true,
itemCount: data.length,
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildResult(index),
separatorBuilder: (context, index) => verticalSpaceSmall,
);
Widget _buildResult(int index) =>
PracticeResultCard(index: index + 1, data: data[index]);
}

View File

@ -4,9 +4,11 @@ import 'package:yimaru_app/ui/widgets/custom_back_button.dart';
class SmallAppBar extends StatelessWidget { class SmallAppBar extends StatelessWidget {
final String? title; final String? title;
final bool showBackButton;
final GestureTapCallback? onTap; final GestureTapCallback? onTap;
const SmallAppBar({super.key, this.onTap, this.title}); const SmallAppBar(
{super.key, this.onTap, this.title, required this.showBackButton});
@override @override
Widget build(BuildContext context) => _buildAppBar(); Widget build(BuildContext context) => _buildAppBar();
@ -16,8 +18,10 @@ class SmallAppBar extends StatelessWidget {
children: _buildAppBarChildren(), children: _buildAppBarChildren(),
); );
List<Widget> _buildAppBarChildren() => List<Widget> _buildAppBarChildren() => [
[_buildBackButtonWrapper(), if (title != null) _buildTitleWrapper()]; if (showBackButton) _buildBackButtonWrapper(),
if (title != null) _buildTitleWrapper()
];
Widget _buildBackButtonWrapper() => Align( Widget _buildBackButtonWrapper() => Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,

View File

@ -4,17 +4,17 @@ import '../common/app_colors.dart';
class SpeakingPartnerImage extends StatelessWidget { class SpeakingPartnerImage extends StatelessWidget {
final double radius; final double radius;
const SpeakingPartnerImage({super.key,required this.radius}); const SpeakingPartnerImage({super.key, required this.radius});
@override @override
Widget build(BuildContext context) => _buildProfileImage(); Widget build(BuildContext context) => _buildProfileImage();
Widget _buildProfileImage() => CircleAvatar( Widget _buildProfileImage() => CircleAvatar(
radius: radius, radius: radius,
backgroundColor: kcViolet, backgroundColor: kcViolet,
backgroundImage: _buildImageBuilder(), backgroundImage: _buildImageBuilder(),
); );
AssetImage? _buildImageBuilder() => const AssetImage('assets/images/profile.png'); AssetImage? _buildImageBuilder() =>
const AssetImage('assets/images/profile.png');
} }

File diff suppressed because it is too large Load Diff