fix: Apply UAT comments

This commit is contained in:
BisratHailu 2026-05-09 01:04:53 +03:00
parent 92db2453c5
commit e917209d10
41 changed files with 605 additions and 456 deletions

BIN
assets/images/pattern.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -510,8 +510,8 @@ class StackedRouter extends _i1.RouterBase {
_i22.LearnLessonDetailView: (data) {
final args = data.getArgs<LearnLessonDetailViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) =>
_i22.LearnLessonDetailView(key: args.key, lesson: args.lesson),
builder: (context) => _i22.LearnLessonDetailView(
key: args.key, lesson: args.lesson, hasPractice: args.hasPractice),
settings: data,
);
},
@ -519,7 +519,13 @@ class StackedRouter extends _i1.RouterBase {
final args = data.getArgs<LearnPracticeViewArguments>(nullOk: false);
return _i37.MaterialPageRoute<dynamic>(
builder: (context) => _i23.LearnPracticeView(
key: args.key, id: args.id, practice: args.practice),
key: args.key,
level: args.level,
id: args.id,
label: args.label,
title: args.title,
practice: args.practice,
subtitle: args.subtitle),
settings: data,
);
},
@ -1098,56 +1104,85 @@ class LearnLessonDetailViewArguments {
const LearnLessonDetailViewArguments({
this.key,
required this.lesson,
required this.hasPractice,
});
final _i37.Key? key;
final _i40.LearnLesson lesson;
final bool hasPractice;
@override
String toString() {
return '{"key": "$key", "lesson": "$lesson"}';
return '{"key": "$key", "lesson": "$lesson", "hasPractice": "$hasPractice"}';
}
@override
bool operator ==(covariant LearnLessonDetailViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.lesson == lesson;
return other.key == key &&
other.lesson == lesson &&
other.hasPractice == hasPractice;
}
@override
int get hashCode {
return key.hashCode ^ lesson.hashCode;
return key.hashCode ^ lesson.hashCode ^ hasPractice.hashCode;
}
}
class LearnPracticeViewArguments {
const LearnPracticeViewArguments({
this.key,
this.level,
required this.id,
required this.label,
required this.title,
required this.practice,
required this.subtitle,
});
final _i37.Key? key;
final String? level;
final int id;
final String label;
final String title;
final _i41.LearnPractices practice;
final String subtitle;
@override
String toString() {
return '{"key": "$key", "id": "$id", "practice": "$practice"}';
return '{"key": "$key", "level": "$level", "id": "$id", "label": "$label", "title": "$title", "practice": "$practice", "subtitle": "$subtitle"}';
}
@override
bool operator ==(covariant LearnPracticeViewArguments other) {
if (identical(this, other)) return true;
return other.key == key && other.id == id && other.practice == practice;
return other.key == key &&
other.level == level &&
other.id == id &&
other.label == label &&
other.title == title &&
other.practice == practice &&
other.subtitle == subtitle;
}
@override
int get hashCode {
return key.hashCode ^ id.hashCode ^ practice.hashCode;
return key.hashCode ^
level.hashCode ^
id.hashCode ^
label.hashCode ^
title.hashCode ^
practice.hashCode ^
subtitle.hashCode;
}
}
@ -1817,6 +1852,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnLessonDetailView({
_i37.Key? key,
required _i40.LearnLesson lesson,
required bool hasPractice,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
@ -1824,7 +1860,8 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return navigateTo<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson),
arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, hasPractice: hasPractice),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -1833,8 +1870,12 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> navigateToLearnPracticeView({
_i37.Key? key,
String? level,
required int id,
required String label,
required String title,
required _i41.LearnPractices practice,
required String subtitle,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
@ -1842,8 +1883,14 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return navigateTo<dynamic>(Routes.learnPracticeView,
arguments:
LearnPracticeViewArguments(key: key, id: id, practice: practice),
arguments: LearnPracticeViewArguments(
key: key,
level: level,
id: id,
label: label,
title: title,
practice: practice,
subtitle: subtitle),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -2395,6 +2442,7 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnLessonDetailView({
_i37.Key? key,
required _i40.LearnLesson lesson,
required bool hasPractice,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
@ -2402,7 +2450,8 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return replaceWith<dynamic>(Routes.learnLessonDetailView,
arguments: LearnLessonDetailViewArguments(key: key, lesson: lesson),
arguments: LearnLessonDetailViewArguments(
key: key, lesson: lesson, hasPractice: hasPractice),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,
@ -2411,8 +2460,12 @@ extension NavigatorStateExtension on _i46.NavigationService {
Future<dynamic> replaceWithLearnPracticeView({
_i37.Key? key,
String? level,
required int id,
required String label,
required String title,
required _i41.LearnPractices practice,
required String subtitle,
int? routerId,
bool preventDuplicates = true,
Map<String, String>? parameters,
@ -2420,8 +2473,14 @@ extension NavigatorStateExtension on _i46.NavigationService {
transition,
}) async {
return replaceWith<dynamic>(Routes.learnPracticeView,
arguments:
LearnPracticeViewArguments(key: key, id: id, practice: practice),
arguments: LearnPracticeViewArguments(
key: key,
level: level,
id: id,
label: label,
title: title,
practice: practice,
subtitle: subtitle),
id: routerId,
preventDuplicates: preventDuplicates,
parameters: parameters,

View File

@ -158,6 +158,7 @@ class DioService {
return true;
} catch (e) {
print('REFRESH EXCEPTION: ${e.toString()}');
await _authenticationService.logout();
await _navigationService.replaceWithLoginView();
return false;

View File

@ -3,6 +3,4 @@ import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';
class PhoneCallerService {
Future<void> call(String phone) async =>
await FlutterPhoneDirectCaller.callNumber(phone);
}

View File

@ -1,4 +1,3 @@
const String ksHomeBottomSheetTitle = 'Build Great Apps!';
const String ksSuggestion =

View File

@ -238,6 +238,12 @@ TextStyle style25P600 = const TextStyle(
fontWeight: FontWeight.w600,
);
TextStyle style40P900 = const TextStyle(
fontSize: 40,
color: kcPrimaryColor,
fontWeight: FontWeight.w900,
);
TextStyle style25DG600 = const TextStyle(
fontSize: 25,
color: kcDarkGrey,
@ -309,16 +315,14 @@ TextStyle style14LG400 = const TextStyle(
TextStyle style14MG400 = const TextStyle(
color: kcMediumGrey,
);
TextStyle style14DG400 = const TextStyle(color: kcDarkGrey);
TextStyle style14DG500 =
const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500);
TextStyle style18MG500 = const TextStyle(
fontSize: 18, color: kcMediumGrey, fontWeight: FontWeight.w500);
TextStyle style14DG400 = const TextStyle(
color: kcDarkGrey,
);
TextStyle style14DG600 = const TextStyle(
color: kcDarkGrey,
fontWeight: FontWeight.w600,

View File

@ -53,62 +53,61 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
controller: viewModel.pageController,
itemCount: viewModel.assessmentQuestions.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (cotext, index) =>
_buildBodyScroller( viewModel),
itemBuilder: (cotext, index) => _buildBodyScroller(viewModel),
);
Widget _buildBodyScroller(
AssessmentViewModel viewModel) =>
Widget _buildBodyScroller(AssessmentViewModel viewModel) =>
SingleChildScrollView(
child: _buildBody(viewModel),
);
Widget _buildBody(
AssessmentViewModel viewModel) =>
Column(
Widget _buildBody(AssessmentViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildBodyChildren(viewModel),
);
List<Widget> _buildBodyChildren(
AssessmentViewModel viewModel) =>[
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) => [
verticalSpaceMedium,
_buildTitleState(viewModel),
verticalSpaceMedium,
_buildAnswersState(viewModel),
_buildContinueButtonWrapper(viewModel)
];
Widget _buildTitleState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex ==
viewModel.assessmentQuestions.length ?Container(): _buildTitle(viewModel);
Widget _buildTitle(
AssessmentViewModel viewModel) =>
Text(
Widget _buildTitleState(AssessmentViewModel viewModel) =>
viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length
? Container()
: _buildTitle(viewModel);
Widget _buildTitle(AssessmentViewModel viewModel) => Text(
'Q${viewModel.currentQuestionIndex + 1}. ${viewModel.assessmentQuestions[viewModel.currentQuestionIndex].questionText} ',
style: style16DG600,
);
Widget _buildAnswersState( AssessmentViewModel viewModel)=> viewModel.currentQuestionIndex ==
viewModel.assessmentQuestions.length ?Container(): _buildAnswers(viewModel);
Widget _buildAnswersState(AssessmentViewModel viewModel) =>
viewModel.currentQuestionIndex == viewModel.assessmentQuestions.length
? Container()
: _buildAnswers(viewModel);
Widget _buildAnswers(
AssessmentViewModel viewModel) =>
ListView.builder(
Widget _buildAnswers(AssessmentViewModel viewModel) => ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?.length,
itemCount: viewModel.assessmentQuestions[viewModel.currentQuestionIndex]
.options?.length,
itemBuilder: (context, inner) => _buildAnswer(
onTap: () => viewModel.setSelectedAnswer(
question: viewModel.currentQuestionIndex + 1,
option: viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner]),
title:
viewModel.assessmentQuestions[viewModel.currentQuestionIndex].options?[inner].optionText ??
option: viewModel
.assessmentQuestions[viewModel.currentQuestionIndex]
.options?[inner]),
title: viewModel.assessmentQuestions[viewModel.currentQuestionIndex]
.options?[inner].optionText ??
'',
selected: viewModel.isSelectedAnswer(
question: viewModel.currentQuestionIndex + 1,
answer: viewModel
.assessmentQuestions[viewModel.currentQuestionIndex ].options?[inner].optionText ??
.assessmentQuestions[viewModel.currentQuestionIndex]
.options?[inner]
.optionText ??
''),
),
);
@ -123,15 +122,12 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
selected: selected,
);
Widget _buildContinueButtonWrapper(
AssessmentViewModel viewModel) =>
Padding(
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
padding: const EdgeInsets.only(bottom: 50),
child: _buildContinueButton(viewModel),
);
Widget _buildContinueButton(
AssessmentViewModel viewModel) =>
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
@ -140,13 +136,13 @@ class AssessmentQuestionsScreen extends ViewModelWidget<AssessmentViewModel> {
viewModel.assessmentQuestions.length - 1
? 'Finish Level'
: 'Continue',
backgroundColor:
viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}')
backgroundColor: viewModel.selectedAnswers
.containsKey('${viewModel.currentQuestionIndex + 1}')
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),
onTap: viewModel.selectedAnswers.containsKey('${viewModel.currentQuestionIndex + 1}')
onTap: viewModel.selectedAnswers
.containsKey('${viewModel.currentQuestionIndex + 1}')
? () => viewModel.nextQuestion()
: null,
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart';
@ -83,9 +84,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
verticalSpaceMedium,
_buildTitle(),
verticalSpaceMedium,
_buildSubtitle('+2519012345678'),
verticalSpaceSmall,
_buildSubtitle('+2519012345678'),
_buildSubtitle(kPhoneSupport),
];
Widget _buildIcon() =>
@ -109,12 +108,13 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
);
Widget _buildContinueButton(CallSupportViewModel viewModel) =>
const CustomElevatedButton(
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Tap to Call',
leadingIcon: Icons.call,
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.callSupport(),
);
}

View File

@ -2,11 +2,18 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../services/phone_caller_service.dart';
import '../../common/app_constants.dart';
class CallSupportViewModel extends BaseViewModel {
// Dependency injection
final _navigationService = locator<NavigationService>();
final _phoneCallerService = locator<PhoneCallerService>();
// Call support
Future<void> callSupport() => _phoneCallerService.call(kPhoneSupport);
// Navigation
void pop() => _navigationService.back();
}

View File

@ -40,14 +40,6 @@ class ForgetPasswordViewModel extends FormViewModel {
bool get length => _length;
bool _number = false;
bool get number => _number;
bool _specialChar = false;
bool get specialChar => _specialChar;
bool _focusPassword = false;
bool get focusPassword => _focusPassword;
@ -105,11 +97,9 @@ class ForgetPasswordViewModel extends FormViewModel {
int completed = 0;
if (_length) completed++;
if (_number) completed++;
if (_specialChar) completed++;
if (_passwordMatch) completed++;
return completed / 4; // returns 0.0 1.0
return completed / 2; // returns 0.0 1.0
}
void validatePassword(
@ -120,17 +110,7 @@ class ForgetPasswordViewModel extends FormViewModel {
_length = false;
}
if (RegExp(r'\d').hasMatch(password)) {
_number = true;
} else {
_number = false;
}
if (RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password)) {
_specialChar = true;
} else {
_specialChar = false;
}
if (password == confirmPassword) {
_passwordMatch = true;
@ -156,8 +136,6 @@ class ForgetPasswordViewModel extends FormViewModel {
// Reset reset password screen
void resetResetPasswordScreen() {
_length = false;
_number = false;
_specialChar = false;
_passwordMatch = false;
_focusPassword = false;
_focusResetCode = false;

View File

@ -140,8 +140,6 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
_buildLinearProgressIndicator(viewModel),
verticalSpaceSmall,
_buildCharLengthValidator(viewModel),
_buildNumberValidator(viewModel),
_buildSymbolValidator(viewModel),
_buildPasswordMatchValidator(viewModel),
verticalSpaceSmall,
_buildSignUpButton(viewModel),
@ -256,16 +254,6 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey,
label: '8 characters minimum');
Widget _buildNumberValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile(
backgroundColor: viewModel.number ? kcPrimaryColor : kcLightGrey,
label: 'a number');
Widget _buildSymbolValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile(
backgroundColor: viewModel.specialChar ? kcPrimaryColor : kcLightGrey,
label: 'one symbol minimum');
Widget _buildPasswordMatchValidator(ForgetPasswordViewModel viewModel) =>
ValidatorListTile(
backgroundColor:
@ -281,20 +269,14 @@ class ResetPasswordScreen extends ViewModelWidget<ForgetPasswordViewModel> {
onTap: passwordController.text.isNotEmpty &&
confirmPasswordController.text.isNotEmpty &&
resetCodeController.text.isNotEmpty &&
viewModel.number &&
viewModel.length &&
viewModel.specialChar &&
viewModel.specialChar &&
viewModel.passwordMatch
? () async => await _reset(viewModel)
: null,
backgroundColor: passwordController.text.isNotEmpty &&
confirmPasswordController.text.isNotEmpty &&
resetCodeController.text.isNotEmpty &&
viewModel.number &&
viewModel.length &&
viewModel.specialChar &&
viewModel.specialChar &&
viewModel.passwordMatch
? kcPrimaryColor
: kcPrimaryColor.withOpacity(0.1),

View File

@ -84,8 +84,9 @@ class LearnCourseView extends StackedView<LearnCourseViewModel> {
course: viewModel.learnCourses[index],
onViewTap: () async => await viewModel
.navigateToLearnModule(viewModel.learnCourses[index]),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.learnCourses[index].id ?? 0),
onPracticeTap: () async => await viewModel.navigateToLearnPractice(
id: viewModel.learnCourses[index].id ?? 0,
level: viewModel.learnCourses[index].name ?? ''),
),
separatorBuilder: (context, index) => verticalSpaceSmall,
);

View File

@ -27,8 +27,16 @@ class LearnCourseViewModel extends BaseViewModel {
Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course);
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.course);
Future<void> navigateToLearnPractice(
{required int id, required String level}) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
level: level,
label: 'Begin Level Practice',
practice: LearnPractices.course,
title: 'Lets Practice Course $level',
subtitle: 'Lets quickly review what youve learned in this level!',
);
// Remote api call

View File

@ -115,9 +115,9 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
_buildSubtitle(),
verticalSpaceSmall,
_buildModuleProgress(),
verticalSpaceMedium,
verticalSpaceLarge,
_buildMotivationCard(),
verticalSpaceMedium,
verticalSpaceLarge,
_buildHeader(),
verticalSpaceMedium,
_buildListViewBuilder(viewModel),
@ -156,20 +156,25 @@ class LearnLessonView extends StackedView<LearnLessonViewModel> {
itemCount: viewModel.lessons.length,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
index: index,
lesson: viewModel.lessons[index],
onLessonTap: () async => await viewModel
.navigateToLearnLessonDetail(viewModel.lessons[index]),
onLessonTap: () async => await viewModel.navigateToLearnLessonDetail(
lesson: viewModel.lessons[index],
hasPractice:
index != viewModel.lessons.length - 1 ? true : false),
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.lessons[index].id ?? 0),
),
);
Widget _buildTile({
required int index,
required LearnLesson lesson,
required GestureTapCallback? onLessonTap,
required GestureTapCallback? onPracticeTap,
}) =>
LearnLessonTile(
index: index,
lesson: lesson,
onLessonTap: onLessonTap,
onPracticeTap: onPracticeTap,

View File

@ -24,11 +24,20 @@ class LearnLessonViewModel extends BaseViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson);
Future<void> navigateToLearnPractice(int id) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Start Practice',
practice: LearnPractices.lesson,
title: 'Let\'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
Future<void> navigateToLearnLessonDetail(LearnLesson lesson) async =>
await _navigationService.navigateToLearnLessonDetailView(lesson: lesson);
Future<void> navigateToLearnLessonDetail(
{required bool hasPractice, required LearnLesson lesson}) async =>
await _navigationService.navigateToLearnLessonDetailView(
lesson: lesson, hasPractice: hasPractice);
// Remote api call

View File

@ -13,9 +13,11 @@ import '../../widgets/small_app_bar.dart';
import 'learn_lesson_detail_viewmodel.dart';
class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
final bool hasPractice;
final LearnLesson lesson;
const LearnLessonDetailView({Key? key, required this.lesson})
const LearnLessonDetailView(
{Key? key, required this.lesson, required this.hasPractice})
: super(key: key);
Future<void> _navigate(LearnLessonDetailViewModel viewModel) async {
@ -86,7 +88,7 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
List<Widget> _buildBodyColumnChildren(LearnLessonDetailViewModel viewModel) =>
[
_buildLevelsColumnWrapper(viewModel),
_buildContinueButtonWrapper(viewModel)
if (hasPractice) _buildPracticeButtonWrapper(viewModel)
];
Widget _buildLevelsColumnWrapper(LearnLessonDetailViewModel viewModel) =>
@ -157,21 +159,21 @@ class LearnLessonDetailView extends StackedView<LearnLessonDetailViewModel> {
style: style14DG400,
);
Widget _buildContinueButtonWrapper(LearnLessonDetailViewModel viewModel) =>
Widget _buildPracticeButtonWrapper(LearnLessonDetailViewModel viewModel) =>
Padding(
padding: const EdgeInsets.only(
left: 15,
right: 15,
bottom: 50,
),
child: _buildContinueButton(viewModel),
child: _buildPracticeButton(viewModel),
);
Widget _buildContinueButton(LearnLessonDetailViewModel viewModel) =>
Widget _buildPracticeButton(LearnLessonDetailViewModel viewModel) =>
CustomElevatedButton(
height: 55,
borderRadius: 12,
text: 'Practices',
text: 'Take Practice',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel),

View File

@ -67,6 +67,13 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// Navigation
void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson);
Future<void> navigateToLearnPractice(int id) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Start Practice',
practice: LearnPractices.lesson,
title: 'Let\'s practice what you just learnt!',
subtitle:
'Ill ask you a few questions, and you can respond naturally.',
);
}

View File

@ -116,8 +116,9 @@ class LearnModuleView extends StackedView<LearnModuleViewModel> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index],
onPracticeTap: () async => await viewModel
.navigateToLearnPractice(viewModel.modules[index].id ?? 0),
onPracticeTap: () async => await viewModel.navigateToLearnPractice(
id: viewModel.modules[index].id ?? 0,
module: viewModel.modules[index].name ?? ''),
onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]),
),

View File

@ -27,8 +27,15 @@ class LearnModuleViewModel extends BaseViewModel {
Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module);
Future<void> navigateToLearnPractice(int id) async => await _navigationService
.navigateToLearnPracticeView(id: id, practice: LearnPractices.module);
Future<void> navigateToLearnPractice(
{required int id, required String module}) async =>
await _navigationService.navigateToLearnPracticeView(
id: id,
label: 'Begin Module Practice',
practice: LearnPractices.module,
title: 'Lets Practice $module',
subtitle: 'Lets quickly review what youve learned in this module! ',
);
// Remote api call

View File

@ -16,10 +16,21 @@ import 'learn_practice_viewmodel.dart';
class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
final int id;
final String label;
final String title;
final String? level;
final String subtitle;
final LearnPractices practice;
const LearnPracticeView({Key? key, required this.id, required this.practice})
: super(key: key);
const LearnPracticeView({
Key? key,
this.level,
required this.id,
required this.label,
required this.title,
required this.practice,
required this.subtitle,
}) : super(key: key);
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording();
@ -108,10 +119,17 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
_buildLearnPracticeQuestionsScreen(),
_buildFinishLearnPracticeScreen(),
_buildLearnPracticeResultScreen(),
if (practice == LearnPractices.course)
_buildLearnPracticeCompletionScreen()
];
Widget _buildLearnPracticeIntroScreen() => const LearnPracticeIntroScreen();
Widget _buildLearnPracticeIntroScreen() => LearnPracticeIntroScreen(
level: level,
title: title,
label: label,
practice: practice,
subtitle: subtitle,
);
Widget _buildLearnPracticeElementsScreen() =>
const LearnPracticeDescriptionScreen();
@ -121,8 +139,9 @@ class LearnPracticeView extends StackedView<LearnPracticeViewModel> {
Widget _buildFinishLearnPracticeScreen() => const FinishLearnPracticeScreen();
Widget _buildLearnPracticeResultScreen() => const LearnPracticeResultScreen();
Widget _buildLearnPracticeResultScreen() =>
LearnPracticeResultScreen(practice: practice);
Widget _buildLearnPracticeCompletionScreen() =>
const LearnPracticeCompletionScreen();
LearnPracticeCompletionScreen(level: level ?? '');
}

View File

@ -225,7 +225,7 @@ class LearnPracticeViewModel extends ReactiveViewModel {
await stopRecording();
_answers.add({
'busy_object': question.id.toString(),
'sample_text_answer': question.audioCorrectAnswerText,
'question_text': question.questionText,
'sample_voice_answer': question.sampleAnswerVoicePrompt,
'recorded_voice_answer': await _voiceRecorderService.getRecordedAudio(),
});

View File

@ -9,7 +9,8 @@ import '../../../widgets/custom_elevated_button.dart';
class LearnPracticeCompletionScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeCompletionScreen({super.key});
final String level;
const LearnPracticeCompletionScreen({super.key, required this.level});
@override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
@ -54,7 +55,7 @@ class LearnPracticeCompletionScreen
);
Widget _buildTitle() => Text(
'Yay, youve completed A1 ',
'Yay, youve completed $level ',
style: style25DG600,
textAlign: TextAlign.center,
);

View File

@ -3,6 +3,7 @@ import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart';
@ -10,7 +11,19 @@ import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart';
class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeIntroScreen({super.key});
final String title;
final String label;
final String? level;
final String subtitle;
final LearnPractices practice;
const LearnPracticeIntroScreen(
{super.key,
this.level,
required this.label,
required this.title,
required this.subtitle,
required this.practice});
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording();
@ -114,21 +127,44 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) =>
[
verticalSpaceMassive,
_buildImage(),
_buildImageState(),
verticalSpaceMedium,
_buildPartnerName(),
_buildPartnerNameState(),
verticalSpaceMedium,
_buildTitle(),
verticalSpaceMedium,
_buildSubtitle()
];
Widget _buildImage() => const SpeakingPartnerImage(
Widget _buildImageState() =>
level != null ? _buildCourseSpeakingPartnerImage() : _buildImage(75);
Widget _buildCourseSpeakingPartnerImage() => CircleAvatar(
radius: 75,
backgroundColor: kcPrimaryColorLight,
child: _buildCourseSpeakingPartnerText(),
);
Widget _buildCourseSpeakingPartnerText() => Text(
level?.toUpperCase() ?? '',
style: style40P900,
);
Widget _buildImage(double radius) => SpeakingPartnerImage(radius: radius);
Widget _buildPartnerNameState() =>
level != null ? _buildCourseSpeakingPartner() : _buildPartnerName();
Widget _buildCourseSpeakingPartner() => Row(
mainAxisSize: MainAxisSize.min,
children: _buildCourseSpeakingPartnerChildren(),
);
List<Widget> _buildCourseSpeakingPartnerChildren() =>
[_buildImage(15), horizontalSpaceTiny, _buildPartnerName()];
Widget _buildPartnerName() => Text.rich(
TextSpan(text: 'Dawit', style: style14DG600, children: [
TextSpan(text: 'Daniel', style: style14DG600, children: [
TextSpan(
text: ' - Your Speaking Partner',
style: style14MG400,
@ -137,13 +173,13 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
);
Widget _buildTitle() => Text(
'Let\'s practice what you just learnt!',
title,
style: style25DG600,
textAlign: TextAlign.center,
);
Widget _buildSubtitle() => Text(
'Ill ask you a few questions, and you can respond naturally.',
subtitle,
maxLines: 1,
style: style14DG400,
textAlign: TextAlign.center,
@ -158,8 +194,8 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton(
height: 55,
text: label,
borderRadius: 12,
text: 'Practice',
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor,

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_tip_section.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart';
@ -12,9 +13,19 @@ import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeResultScreen({super.key});
final LearnPractices practice;
void _navigate(LearnPracticeViewModel viewModel) async =>
const LearnPracticeResultScreen({super.key, required this.practice});
Future<void> _navigate(LearnPracticeViewModel viewModel) async {
if (practice == LearnPractices.course) {
viewModel.goTo(5);
} else {
viewModel.pop();
}
}
Future<void> _retry(LearnPracticeViewModel viewModel) async =>
await viewModel.reset();
Future<void> _cancel(LearnPracticeViewModel viewModel) async {
@ -145,8 +156,8 @@ class LearnPracticeResultScreen
text: 'Continue',
borderRadius: 12,
foregroundColor: kcWhite,
onTap: () => viewModel.goTo(5),
backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel),
);
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>
@ -157,6 +168,6 @@ class LearnPracticeResultScreen
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,
onTap: () => _navigate(viewModel),
onTap: () async => await _retry(viewModel),
);
}

View File

@ -26,10 +26,6 @@ class ProfileViewModel extends ReactiveViewModel {
final _googleAuthService = locator<GoogleAuthService>();
final _phoneCallerService = locator<PhoneCallerService>();
final _urlLauncherService = locator<UrlLauncherService>();
final _imagePickerService = locator<ImagePickerService>();
final _authenticationService = locator<AuthenticationService>();
@ -68,13 +64,6 @@ class ProfileViewModel extends ReactiveViewModel {
}
}
// Launch telegram
Future<void> launchTelegram() =>
_urlLauncherService.launchUri(kTelegramSupport);
// Call support
Future<void> callSupport() => _phoneCallerService.call(kPhoneSupport);
// Dialog
Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog(

View File

@ -67,12 +67,13 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
);
void _checkRegion(ProfileDetailViewModel viewModel) {
bool region = viewModel.checkRegion(region:viewModel.user?.region ?? 'Addis Ababa',country:viewModel.user?.country ?? 'Ethiopia' );
bool region = viewModel.checkRegion(
region: viewModel.user?.region ?? 'Addis Ababa',
country: viewModel.user?.country ?? 'Ethiopia');
if (region) {
viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa');
} else {
regionController.text = viewModel.user?.region ?? '';
}
}
@ -549,10 +550,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildRegionFormState(viewModel),
if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion &&
viewModel.focusRegion) verticalSpaceTiny,
viewModel.focusRegion)
verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion &&
viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel),
viewModel.focusRegion)
_buildRegionValidatorWrapper(viewModel),
];
Widget _buildRegionFormFieldLabel() => CustomFormLabel(
@ -574,7 +577,8 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onChanged: (value) =>
viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => TextFormField(
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
TextFormField(
controller: regionController,
onTap: viewModel.setRegionFocus,
decoration: inputDecoration(
@ -593,7 +597,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
style: style12R700,
);
Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) =>
Column(
mainAxisSize: MainAxisSize.min,
@ -608,7 +611,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildOccupationDropdownLabel(),
verticalSpaceSmall,
_buildOccupationDropdown(viewModel)
];
Widget _buildOccupationDropdownLabel() => CustomFormLabel(

View File

@ -18,7 +18,6 @@ const String RegionValueKey = 'region';
const String PhoneNumberValueKey = 'phoneNumber';
const String LastNameValueKey = 'lastName';
const String FirstNameValueKey = 'firstName';
const String OccupationValueKey = 'occupation';
final Map<String, TextEditingController>
_ProfileDetailViewTextEditingControllers = {};
@ -32,7 +31,6 @@ final Map<String, String? Function(String?)?>
PhoneNumberValueKey: FormValidator.validatePhoneNumberForm,
LastNameValueKey: FormValidator.validateForm,
FirstNameValueKey: FormValidator.validateForm,
OccupationValueKey: FormValidator.validateForm,
};
mixin $ProfileDetailView {
@ -46,15 +44,12 @@ mixin $ProfileDetailView {
_getFormTextEditingController(LastNameValueKey);
TextEditingController get firstNameController =>
_getFormTextEditingController(FirstNameValueKey);
TextEditingController get occupationController =>
_getFormTextEditingController(OccupationValueKey);
FocusNode get emailFocusNode => _getFormFocusNode(EmailValueKey);
FocusNode get regionFocusNode => _getFormFocusNode(RegionValueKey);
FocusNode get phoneNumberFocusNode => _getFormFocusNode(PhoneNumberValueKey);
FocusNode get lastNameFocusNode => _getFormFocusNode(LastNameValueKey);
FocusNode get firstNameFocusNode => _getFormFocusNode(FirstNameValueKey);
FocusNode get occupationFocusNode => _getFormFocusNode(OccupationValueKey);
TextEditingController _getFormTextEditingController(
String key, {
@ -85,7 +80,6 @@ mixin $ProfileDetailView {
phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
}
@ -102,7 +96,6 @@ mixin $ProfileDetailView {
phoneNumberController.addListener(() => _updateFormData(model));
lastNameController.addListener(() => _updateFormData(model));
firstNameController.addListener(() => _updateFormData(model));
occupationController.addListener(() => _updateFormData(model));
_updateFormData(model, forceValidate: _autoTextFieldValidation);
}
@ -117,7 +110,6 @@ mixin $ProfileDetailView {
PhoneNumberValueKey: phoneNumberController.text,
LastNameValueKey: lastNameController.text,
FirstNameValueKey: firstNameController.text,
OccupationValueKey: occupationController.text,
}),
);
@ -165,8 +157,6 @@ extension ValueProperties on FormStateHelper {
this.formValueMap[PhoneNumberValueKey] as String?;
String? get lastNameValue => this.formValueMap[LastNameValueKey] as String?;
String? get firstNameValue => this.formValueMap[FirstNameValueKey] as String?;
String? get occupationValue =>
this.formValueMap[OccupationValueKey] as String?;
set emailValue(String? value) {
this.setData(
@ -226,18 +216,6 @@ extension ValueProperties on FormStateHelper {
}
}
set occupationValue(String? value) {
this.setData(
this.formValueMap..addAll({OccupationValueKey: value}),
);
if (_ProfileDetailViewTextEditingControllers.containsKey(
OccupationValueKey)) {
_ProfileDetailViewTextEditingControllers[OccupationValueKey]?.text =
value ?? '';
}
}
bool get hasEmail =>
this.formValueMap.containsKey(EmailValueKey) &&
(emailValue?.isNotEmpty ?? false);
@ -253,9 +231,6 @@ extension ValueProperties on FormStateHelper {
bool get hasFirstName =>
this.formValueMap.containsKey(FirstNameValueKey) &&
(firstNameValue?.isNotEmpty ?? false);
bool get hasOccupation =>
this.formValueMap.containsKey(OccupationValueKey) &&
(occupationValue?.isNotEmpty ?? false);
bool get hasEmailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey]?.isNotEmpty ?? false;
@ -267,8 +242,6 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey]?.isNotEmpty ?? false;
bool get hasFirstNameValidationMessage =>
this.fieldsValidationMessages[FirstNameValueKey]?.isNotEmpty ?? false;
bool get hasOccupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey]?.isNotEmpty ?? false;
String? get emailValidationMessage =>
this.fieldsValidationMessages[EmailValueKey];
@ -280,8 +253,6 @@ extension ValueProperties on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey];
String? get firstNameValidationMessage =>
this.fieldsValidationMessages[FirstNameValueKey];
String? get occupationValidationMessage =>
this.fieldsValidationMessages[OccupationValueKey];
}
extension Methods on FormStateHelper {
@ -295,8 +266,6 @@ extension Methods on FormStateHelper {
this.fieldsValidationMessages[LastNameValueKey] = validationMessage;
void setFirstNameValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[FirstNameValueKey] = validationMessage;
void setOccupationValidationMessage(String? validationMessage) =>
this.fieldsValidationMessages[OccupationValueKey] = validationMessage;
/// Clears text input fields on the Form
void clearForm() {
@ -305,7 +274,6 @@ extension Methods on FormStateHelper {
phoneNumberValue = '';
lastNameValue = '';
firstNameValue = '';
occupationValue = '';
}
/// Validates text input fields on the Form
@ -316,7 +284,6 @@ extension Methods on FormStateHelper {
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
});
}
}
@ -341,5 +308,4 @@ void updateValidationData(FormStateHelper model) =>
PhoneNumberValueKey: getValidationMessage(PhoneNumberValueKey),
LastNameValueKey: getValidationMessage(LastNameValueKey),
FirstNameValueKey: getValidationMessage(FirstNameValueKey),
OccupationValueKey: getValidationMessage(OccupationValueKey),
});

View File

@ -57,13 +57,11 @@ class ProfileDetailViewModel extends ReactiveViewModel
bool get focusEmail => _focusEmail;
// Occupation
String _selectedOccupation = 'Students (High school & University)';
String get selectedOccupation => _selectedOccupation;
// Country
String _selectedCountry = 'Ethiopia';
@ -117,8 +115,6 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi();
}
// Occupation
List<String> getOccupations() => [
'Students (High school & University)',

View File

@ -130,7 +130,6 @@ class RegisterViewModel extends ReactiveViewModel
_length = false;
}
if (password == confirmPassword) {
_passwordMatch = true;
} else {
@ -237,6 +236,7 @@ class RegisterViewModel extends ReactiveViewModel
}
void goBack() {
print('HERE');
if (_currentPage == 1) {
_currentPage = 0;
rebuildUi();

View File

@ -213,7 +213,6 @@ class CreatePasswordScreen extends ViewModelWidget<RegisterViewModel> {
backgroundColor: viewModel.length ? kcPrimaryColor : kcLightGrey,
label: '8 characters minimum');
Widget _buildPasswordMatchValidator(RegisterViewModel viewModel) =>
ValidatorListTile(
backgroundColor:

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:yimaru_app/ui/common/app_constants.dart';
import 'package:yimaru_app/ui/widgets/circular_icon.dart';
import '../../common/app_colors.dart';
@ -91,20 +92,16 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
Widget _buildIcon() =>
const CircularIcon(icon: Icons.telegram, size: 50, color: kcSkyBlue);
Widget _buildTitle() => const Text(
Widget _buildTitle() => Text(
'Join Yimaru Academy on Telegram',
style: style25DG600,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
color: kcDarkGrey,
fontWeight: FontWeight.w600,
),
);
Widget _buildSubtitle() => const Text(
Widget _buildSubtitle() => Text(
'Connect with our support team instantly on Telegram for quick assistance and community updates',
style: style14MG400,
textAlign: TextAlign.center,
style: TextStyle(color: kcMediumGrey),
);
Widget _buildLowerColumn(TelegramSupportViewModel viewModel) => Column(
@ -123,30 +120,23 @@ class TelegramSupportView extends StackedView<TelegramSupportViewModel> {
];
Widget _buildContinueButton(TelegramSupportViewModel viewModel) =>
const CustomElevatedButton(
CustomElevatedButton(
height: 55,
borderRadius: 12,
leadingIcon: Icons.telegram,
text: 'Open in Telegram',
foregroundColor: kcWhite,
leadingIcon: Icons.telegram,
backgroundColor: kcPrimaryColor,
onTap: () async => await viewModel.launchTelegram(),
);
Widget _buildOptionTextDivider() => const OptionTextDivider();
Widget _buildSearchText() => const Text.rich(
Widget _buildSearchText() => Text.rich(
TextSpan(text: 'Search for', style: style14DG500, children: [
TextSpan(
text: 'Search for',
style: TextStyle(
color: kcDarkGrey,
),
children: [
TextSpan(
text: ' @YimaruSupport',
style: TextStyle(
color: kcPrimaryColor,
fontWeight: FontWeight.w600,
),
style: style14P600,
text: ' $kTelegramSupport',
)
]),
);

View File

@ -2,8 +2,19 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart';
import '../../../services/url_launcher_service.dart';
import '../../common/app_constants.dart';
class TelegramSupportViewModel extends BaseViewModel {
// Dependency injection
final _navigationService = locator<NavigationService>();
final _urlLauncherService = locator<UrlLauncherService>();
// Launch telegram
Future<void> launchTelegram() =>
_urlLauncherService.launchUri(kTelegramSupport);
// Navigation
void pop() => _navigationService.back();
}

View File

@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
class AppBarPattern extends StatelessWidget {
const AppBarPattern({
super.key,
});
@override
Widget build(BuildContext context) => _buildDecorationImageWrapper();
Widget _buildDecorationImageWrapper() => ClipRRect(
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
child: _buildDecorationImage(),
);
Widget _buildDecorationImage() => SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: _buildPatternWrapper(),
);
Widget _buildPatternWrapper() => SizedBox(
width: double.maxFinite,
height: double.maxFinite,
child: _buildPatternMask(),
);
Widget _buildPatternMask() => ShaderMask(
shaderCallback: (Rect bounds) => const LinearGradient(
colors: [kcWhite, kcWhite],
).createShader(bounds),
blendMode: BlendMode.modulate,
child: _buildPattern(),
);
Widget _buildPattern() => Image.asset(
'assets/images/pattern.png',
fit: BoxFit.cover,
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart';
import 'package:yimaru_app/ui/widgets/language_button.dart';
class LargeAppBar extends StatelessWidget {
@ -18,12 +19,11 @@ class LargeAppBar extends StatelessWidget {
required this.showLanguageSelection});
@override
Widget build(BuildContext context) => _buildAppBarWrapper();
Widget build(BuildContext context) => _buildStackWrapper();
Widget _buildAppBarWrapper() => Container(
Widget _buildStackWrapper() => Container(
height: 125,
width: double.maxFinite,
alignment: Alignment.bottomCenter,
decoration: const BoxDecoration(
color: kcPrimaryColor,
borderRadius: BorderRadius.only(
@ -31,6 +31,18 @@ class LargeAppBar extends StatelessWidget {
bottomRight: Radius.circular(24),
),
),
child: _buildStack(),
);
Widget _buildStack() => Stack(
children: [ _buildPattern(),_buildAppBarWrapper()],
);
Widget _buildAppBarWrapper() => Container(
color: kcTransparent,
width: double.maxFinite,
height: double.maxFinite,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 25, right: 15),
child: _buildAppBarItems(),
);
@ -74,4 +86,6 @@ class LargeAppBar extends StatelessWidget {
Icons.close,
color: kcWhite,
);
Widget _buildPattern() => const AppBarPattern();
}

View File

@ -143,7 +143,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
height: 15,
borderRadius: 12,
onTap: onViewTap,
text: 'View Courses',
text: 'View Course',
foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor,
);
@ -158,7 +158,7 @@ class LearnCourseTile extends ViewModelWidget<LearnCourseViewModel> {
height: 15,
borderRadius: 12,
onTap: onPracticeTap,
text: 'Take Practices',
text: 'Take Practice',
backgroundColor: kcWhite,
borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor,

View File

@ -12,12 +12,16 @@ import 'custom_elevated_button.dart';
import 'custom_linear_progress_indicator.dart';
class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
final int index;
final LearnLesson lesson;
final GestureTapCallback? onLessonTap;
final GestureTapCallback? onPracticeTap;
const LearnLessonTile(
{super.key, this.onLessonTap, this.onPracticeTap, required this.lesson});
const LearnLessonTile({super.key,
this.onLessonTap,
this.onPracticeTap,
required this.index,
required this.lesson});
@override
Widget build(BuildContext context, LearnLessonViewModel viewModel) =>
@ -52,8 +56,8 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
expandedAlignment: Alignment.centerLeft,
backgroundColor: kcPrimaryColor.withOpacity(0.1),
controlAffinity: ListTileControlAffinity.trailing,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 0),
expandedCrossAxisAlignment: CrossAxisAlignment.start,
tilePadding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
collapsedBackgroundColor: kcPrimaryColor.withOpacity(0.1),
childrenPadding: const EdgeInsets.fromLTRB(15, 0, 15, 15),
// enabled: (lesson.access?.isAccessible ?? false),
@ -130,10 +134,11 @@ class LearnLessonTile extends ViewModelWidget<LearnLessonViewModel> {
Widget _buildActionButtons(LearnLessonViewModel viewModel) => Row(
mainAxisAlignment: MainAxisAlignment.end,
children: __buildActionButtonChildren(viewModel),
children: _buildActionButtonChildren(viewModel),
);
List<Widget> __buildActionButtonChildren(LearnLessonViewModel viewModel) => [
List<Widget> _buildActionButtonChildren(LearnLessonViewModel viewModel) => [
if (index != viewModel.lessons.length - 1)
_buildPracticeButtonWrapper(viewModel),
horizontalSpaceSmall,
_buildLessonButtonWrapper(viewModel)

View File

@ -15,7 +15,7 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
_buildColumnWrapper(viewModel);
Widget _buildColumnWrapper(LearnPracticeViewModel viewModel) => SizedBox(
height: 100,
height: 125,
width: double.maxFinite,
child: _buildColumn(viewModel),
);
@ -30,7 +30,8 @@ class LearnPracticeResultCard extends ViewModelWidget<LearnPracticeViewModel> {
[_buildQuestion(viewModel), verticalSpaceSmall, _buildRow()];
Widget _buildQuestion(LearnPracticeViewModel viewModel) => Text(
answer['sample_text_answer'],
answer['question_text'],
maxLines: 2,
style: style14DG400,
);

View File

@ -14,8 +14,7 @@ class MiniThumbnail extends StatelessWidget {
Widget build(BuildContext context) => _buildWrapper();
Widget _buildWrapper() => SizedBox(
width: 75,
height: double.maxFinite,
width: 80,
child: _buildLeadingClipper(),
);
@ -40,15 +39,17 @@ class MiniThumbnail extends StatelessWidget {
: _buildNetworkImage();
Widget _buildNetworkImage() => CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: thumbnail,
fit: BoxFit.fill,
width: double.maxFinite,
height: double.maxFinite,
);
Widget _buildLocalImage() => Image.asset(
thumbnail,
fit: BoxFit.fill,
fit: BoxFit.cover,
width: double.maxFinite,
height: double.maxFinite,
);
Widget _buildPlayButtonWrapper() => Align(

View File

@ -1,5 +1,5 @@
name: yimaru_app
version: 0.1.13+15
version: 0.1.14+16
publish_to: 'none'
description: A new Flutter project.