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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
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/common/app_constants.dart';
import '../../common/app_colors.dart'; import '../../common/app_colors.dart';
import '../../common/ui_helpers.dart'; import '../../common/ui_helpers.dart';
@ -83,9 +84,7 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildSubtitle('+2519012345678'), _buildSubtitle(kPhoneSupport),
verticalSpaceSmall,
_buildSubtitle('+2519012345678'),
]; ];
Widget _buildIcon() => Widget _buildIcon() =>
@ -109,12 +108,13 @@ class CallSupportView extends StackedView<CallSupportViewModel> {
); );
Widget _buildContinueButton(CallSupportViewModel viewModel) => Widget _buildContinueButton(CallSupportViewModel viewModel) =>
const CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
borderRadius: 12, borderRadius: 12,
text: 'Tap to Call', text: 'Tap to Call',
leadingIcon: Icons.call, leadingIcon: Icons.call,
foregroundColor: kcWhite, foregroundColor: kcWhite,
backgroundColor: kcPrimaryColor, 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 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../services/phone_caller_service.dart';
import '../../common/app_constants.dart';
class CallSupportViewModel extends BaseViewModel { class CallSupportViewModel extends BaseViewModel {
// Dependency injection // Dependency injection
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _phoneCallerService = locator<PhoneCallerService>();
// Call support
Future<void> callSupport() => _phoneCallerService.call(kPhoneSupport);
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
} }

View File

@ -40,14 +40,6 @@ class ForgetPasswordViewModel extends FormViewModel {
bool get length => _length; bool get length => _length;
bool _number = false;
bool get number => _number;
bool _specialChar = false;
bool get specialChar => _specialChar;
bool _focusPassword = false; bool _focusPassword = false;
bool get focusPassword => _focusPassword; bool get focusPassword => _focusPassword;
@ -105,11 +97,9 @@ class ForgetPasswordViewModel extends FormViewModel {
int completed = 0; int completed = 0;
if (_length) completed++; if (_length) completed++;
if (_number) completed++;
if (_specialChar) completed++;
if (_passwordMatch) completed++; if (_passwordMatch) completed++;
return completed / 4; // returns 0.0 1.0 return completed / 2; // returns 0.0 1.0
} }
void validatePassword( void validatePassword(
@ -120,17 +110,7 @@ class ForgetPasswordViewModel extends FormViewModel {
_length = false; _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) { if (password == confirmPassword) {
_passwordMatch = true; _passwordMatch = true;
@ -156,8 +136,6 @@ class ForgetPasswordViewModel extends FormViewModel {
// Reset reset password screen // Reset reset password screen
void resetResetPasswordScreen() { void resetResetPasswordScreen() {
_length = false; _length = false;
_number = false;
_specialChar = false;
_passwordMatch = false; _passwordMatch = false;
_focusPassword = false; _focusPassword = false;
_focusResetCode = false; _focusResetCode = false;

View File

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

View File

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

View File

@ -27,8 +27,16 @@ class LearnCourseViewModel extends BaseViewModel {
Future<void> navigateToLearnModule(LearnCourse course) async => Future<void> navigateToLearnModule(LearnCourse course) async =>
_navigationService.navigateToLearnModuleView(course: course); _navigationService.navigateToLearnModuleView(course: course);
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(
.navigateToLearnPracticeView(id: id, practice: LearnPractices.course); {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 // Remote api call

View File

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

View File

@ -24,11 +24,20 @@ class LearnLessonViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(int id) async =>
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); 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 => Future<void> navigateToLearnLessonDetail(
await _navigationService.navigateToLearnLessonDetailView(lesson: lesson); {required bool hasPractice, required LearnLesson lesson}) async =>
await _navigationService.navigateToLearnLessonDetailView(
lesson: lesson, hasPractice: hasPractice);
// Remote api call // Remote api call

View File

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

View File

@ -67,6 +67,13 @@ class LearnLessonDetailViewModel extends BaseViewModel {
// Navigation // Navigation
void pop() => _navigationService.back(); void pop() => _navigationService.back();
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(int id) async =>
.navigateToLearnPracticeView(id: id, practice: LearnPractices.lesson); 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(), physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) => _buildTile( itemBuilder: (context, index) => _buildTile(
module: viewModel.modules[index], module: viewModel.modules[index],
onPracticeTap: () async => await viewModel onPracticeTap: () async => await viewModel.navigateToLearnPractice(
.navigateToLearnPractice(viewModel.modules[index].id ?? 0), id: viewModel.modules[index].id ?? 0,
module: viewModel.modules[index].name ?? ''),
onModuleTap: () async => onModuleTap: () async =>
await viewModel.navigateToLearnLesson(viewModel.modules[index]), await viewModel.navigateToLearnLesson(viewModel.modules[index]),
), ),

View File

@ -27,8 +27,15 @@ class LearnModuleViewModel extends BaseViewModel {
Future<void> navigateToLearnLesson(LearnModule module) async => Future<void> navigateToLearnLesson(LearnModule module) async =>
await _navigationService.navigateToLearnLessonView(module: module); await _navigationService.navigateToLearnLessonView(module: module);
Future<void> navigateToLearnPractice(int id) async => await _navigationService Future<void> navigateToLearnPractice(
.navigateToLearnPracticeView(id: id, practice: LearnPractices.module); {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 // Remote api call

View File

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

View File

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

View File

@ -9,7 +9,8 @@ import '../../../widgets/custom_elevated_button.dart';
class LearnPracticeCompletionScreen class LearnPracticeCompletionScreen
extends ViewModelWidget<LearnPracticeViewModel> { extends ViewModelWidget<LearnPracticeViewModel> {
const LearnPracticeCompletionScreen({super.key}); final String level;
const LearnPracticeCompletionScreen({super.key, required this.level});
@override @override
Widget build(BuildContext context, LearnPracticeViewModel viewModel) => Widget build(BuildContext context, LearnPracticeViewModel viewModel) =>
@ -54,7 +55,7 @@ class LearnPracticeCompletionScreen
); );
Widget _buildTitle() => Text( Widget _buildTitle() => Text(
'Yay, youve completed A1 ', 'Yay, youve completed $level ',
style: style25DG600, style: style25DG600,
textAlign: TextAlign.center, 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 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.dart';
import '../../../common/app_colors.dart'; import '../../../common/app_colors.dart';
import '../../../common/enmus.dart';
import '../../../common/ui_helpers.dart'; import '../../../common/ui_helpers.dart';
import '../../../widgets/cancel_learn_practice_sheet.dart'; import '../../../widgets/cancel_learn_practice_sheet.dart';
import '../../../widgets/custom_elevated_button.dart'; import '../../../widgets/custom_elevated_button.dart';
@ -10,7 +11,19 @@ import '../../../widgets/small_app_bar.dart';
import '../../../widgets/speaking_partner_image.dart'; import '../../../widgets/speaking_partner_image.dart';
class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> { 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 { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
await viewModel.stopRecording(); await viewModel.stopRecording();
@ -114,21 +127,44 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) => List<Widget> _buildPracticeColumnChildren(LearnPracticeViewModel viewModel) =>
[ [
verticalSpaceMassive, verticalSpaceMassive,
_buildImage(), _buildImageState(),
verticalSpaceMedium, verticalSpaceMedium,
_buildPartnerName(), _buildPartnerNameState(),
verticalSpaceMedium, verticalSpaceMedium,
_buildTitle(), _buildTitle(),
verticalSpaceMedium, verticalSpaceMedium,
_buildSubtitle() _buildSubtitle()
]; ];
Widget _buildImage() => const SpeakingPartnerImage( Widget _buildImageState() =>
level != null ? _buildCourseSpeakingPartnerImage() : _buildImage(75);
Widget _buildCourseSpeakingPartnerImage() => CircleAvatar(
radius: 75, 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( Widget _buildPartnerName() => Text.rich(
TextSpan(text: 'Dawit', style: style14DG600, children: [ TextSpan(text: 'Daniel', style: style14DG600, children: [
TextSpan( TextSpan(
text: ' - Your Speaking Partner', text: ' - Your Speaking Partner',
style: style14MG400, style: style14MG400,
@ -137,13 +173,13 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
); );
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,
maxLines: 1, maxLines: 1,
style: style14DG400, style: style14DG400,
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -158,8 +194,8 @@ class LearnPracticeIntroScreen extends ViewModelWidget<LearnPracticeViewModel> {
Widget _buildContinueButton(LearnPracticeViewModel viewModel) => Widget _buildContinueButton(LearnPracticeViewModel viewModel) =>
CustomElevatedButton( CustomElevatedButton(
height: 55, height: 55,
text: label,
borderRadius: 12, borderRadius: 12,
text: 'Practice',
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(1), onTap: () => viewModel.goTo(1),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,

View File

@ -1,5 +1,6 @@
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/common/enmus.dart';
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_viewmodel.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_tip_section.dart';
import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart'; import 'package:yimaru_app/ui/widgets/learn_practice_results_wrapper.dart';
@ -12,9 +13,19 @@ import '../../../widgets/small_app_bar.dart';
class LearnPracticeResultScreen class LearnPracticeResultScreen
extends ViewModelWidget<LearnPracticeViewModel> { 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(); await viewModel.reset();
Future<void> _cancel(LearnPracticeViewModel viewModel) async { Future<void> _cancel(LearnPracticeViewModel viewModel) async {
@ -145,8 +156,8 @@ class LearnPracticeResultScreen
text: 'Continue', text: 'Continue',
borderRadius: 12, borderRadius: 12,
foregroundColor: kcWhite, foregroundColor: kcWhite,
onTap: () => viewModel.goTo(5),
backgroundColor: kcPrimaryColor, backgroundColor: kcPrimaryColor,
onTap: () async => await _navigate(viewModel),
); );
Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) => Widget _buildPracticeAgainButton(LearnPracticeViewModel viewModel) =>
@ -157,6 +168,6 @@ class LearnPracticeResultScreen
backgroundColor: kcWhite, backgroundColor: kcWhite,
borderColor: kcPrimaryColor, borderColor: kcPrimaryColor,
foregroundColor: kcPrimaryColor, foregroundColor: kcPrimaryColor,
onTap: () => _navigate(viewModel), onTap: () async => await _retry(viewModel),
); );
} }

View File

@ -98,9 +98,9 @@ class CountryRegionFormScreen extends ViewModelWidget<OnboardingViewModel> {
_buildCountryDropDown(viewModel), _buildCountryDropDown(viewModel),
verticalSpaceMedium, verticalSpaceMedium,
_buildRegionFormState(viewModel), _buildRegionFormState(viewModel),
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&
viewModel.focusRegion) viewModel.focusRegion)
verticalSpaceTiny, verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&

View File

@ -26,10 +26,6 @@ class ProfileViewModel extends ReactiveViewModel {
final _googleAuthService = locator<GoogleAuthService>(); final _googleAuthService = locator<GoogleAuthService>();
final _phoneCallerService = locator<PhoneCallerService>();
final _urlLauncherService = locator<UrlLauncherService>();
final _imagePickerService = locator<ImagePickerService>(); final _imagePickerService = locator<ImagePickerService>();
final _authenticationService = locator<AuthenticationService>(); 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 // Dialog
Future<bool?> showAbortDialog() async { Future<bool?> showAbortDialog() async {
DialogResponse? response = await _dialogService.showDialog( DialogResponse? response = await _dialogService.showDialog(

View File

@ -66,13 +66,14 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
content: _buildImagePicker(context: context, viewModel: viewModel), content: _buildImagePicker(context: context, viewModel: viewModel),
); );
void _checkRegion(ProfileDetailViewModel viewModel){ void _checkRegion(ProfileDetailViewModel viewModel) {
bool region = viewModel.checkRegion(region:viewModel.user?.region ?? 'Addis Ababa',country:viewModel.user?.country ?? 'Ethiopia' ); bool region = viewModel.checkRegion(
if(region){ region: viewModel.user?.region ?? 'Addis Ababa',
country: viewModel.user?.country ?? 'Ethiopia');
if (region) {
viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa'); viewModel.setSelectedRegion(viewModel.user?.region ?? 'Addis Ababa');
}else{ } else {
regionController.text = viewModel.user?.region ?? ''; regionController.text = viewModel.user?.region ?? '';
} }
} }
@ -549,10 +550,12 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildRegionFormState(viewModel), _buildRegionFormState(viewModel),
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&
viewModel.focusRegion) verticalSpaceTiny, viewModel.focusRegion)
verticalSpaceTiny,
if (viewModel.hasRegionValidationMessage && if (viewModel.hasRegionValidationMessage &&
!viewModel.dropdownRegion && !viewModel.dropdownRegion &&
viewModel.focusRegion) _buildRegionValidatorWrapper(viewModel), viewModel.focusRegion)
_buildRegionValidatorWrapper(viewModel),
]; ];
Widget _buildRegionFormFieldLabel() => CustomFormLabel( Widget _buildRegionFormFieldLabel() => CustomFormLabel(
@ -574,14 +577,15 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onChanged: (value) => onChanged: (value) =>
viewModel.setSelectedRegion(value ?? 'Addis Ababa')); viewModel.setSelectedRegion(value ?? 'Addis Ababa'));
Widget _buildRegionFormField(ProfileDetailViewModel viewModel) => TextFormField( Widget _buildRegionFormField(ProfileDetailViewModel viewModel) =>
controller: regionController, TextFormField(
onTap: viewModel.setRegionFocus, controller: regionController,
decoration: inputDecoration( onTap: viewModel.setRegionFocus,
hint: 'Enter Your City', decoration: inputDecoration(
focus: viewModel.focusRegion, hint: 'Enter Your City',
filled: regionController.text.isNotEmpty), focus: viewModel.focusRegion,
); filled: regionController.text.isNotEmpty),
);
Widget _buildRegionValidatorWrapper(ProfileDetailViewModel viewModel) => Widget _buildRegionValidatorWrapper(ProfileDetailViewModel viewModel) =>
viewModel.hasRegionValidationMessage viewModel.hasRegionValidationMessage
@ -589,10 +593,9 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
: Container(); : Container();
Widget _buildRegionValidator(ProfileDetailViewModel viewModel) => Text( Widget _buildRegionValidator(ProfileDetailViewModel viewModel) => Text(
viewModel.regionValidationMessage!, viewModel.regionValidationMessage!,
style: style12R700, style: style12R700,
); );
Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) => Widget _buildOccupationDropdownWrapper(ProfileDetailViewModel viewModel) =>
Column( Column(
@ -608,7 +611,6 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
_buildOccupationDropdownLabel(), _buildOccupationDropdownLabel(),
verticalSpaceSmall, verticalSpaceSmall,
_buildOccupationDropdown(viewModel) _buildOccupationDropdown(viewModel)
]; ];
Widget _buildOccupationDropdownLabel() => CustomFormLabel( Widget _buildOccupationDropdownLabel() => CustomFormLabel(
@ -625,9 +627,9 @@ class ProfileDetailView extends StackedView<ProfileDetailViewModel>
onChanged: (value) => viewModel.setSelectedOccupation( onChanged: (value) => viewModel.setSelectedOccupation(
value ?? 'Students (High school & University)')); value ?? 'Students (High school & University)'));
Icon _buildSearchIcon() => const Icon( Icon _buildSearchIcon() => const Icon(
Icons.search, Icons.search,
color: kcPrimaryColor, color: kcPrimaryColor,
); );
Widget _buildLowerColumn(ProfileDetailViewModel viewModel) => Column( Widget _buildLowerColumn(ProfileDetailViewModel viewModel) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: _buildLowerColumnChildren(viewModel), children: _buildLowerColumnChildren(viewModel),

View File

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

View File

@ -57,13 +57,11 @@ class ProfileDetailViewModel extends ReactiveViewModel
bool get focusEmail => _focusEmail; bool get focusEmail => _focusEmail;
// Occupation // Occupation
String _selectedOccupation = 'Students (High school & University)'; String _selectedOccupation = 'Students (High school & University)';
String get selectedOccupation => _selectedOccupation; String get selectedOccupation => _selectedOccupation;
// Country // Country
String _selectedCountry = 'Ethiopia'; String _selectedCountry = 'Ethiopia';
@ -117,18 +115,16 @@ class ProfileDetailViewModel extends ReactiveViewModel
rebuildUi(); rebuildUi();
} }
// Occupation // Occupation
List<String> getOccupations() => [ List<String> getOccupations() => [
'Students (High school & University)', 'Students (High school & University)',
'Job Seekers / Fresh Graduates', 'Job Seekers / Fresh Graduates',
'Working Professionals (Corporate/Office)', 'Working Professionals (Corporate/Office)',
'Government & NGO Workers', 'Government & NGO Workers',
'Entrepreneurs & Small Business Owners', 'Entrepreneurs & Small Business Owners',
'Hospitality & Tourism Workers', 'Hospitality & Tourism Workers',
'Freelancers / Remote Workers (Digital Economy)' 'Freelancers / Remote Workers (Digital Economy)'
]; ];
void setSelectedOccupation(String value) { void setSelectedOccupation(String value) {
_selectedOccupation = value; _selectedOccupation = value;
@ -137,158 +133,158 @@ class ProfileDetailViewModel extends ReactiveViewModel
// Country // Country
List<String> getCountries() => [ List<String> getCountries() => [
"Afghanistan", "Afghanistan",
"Albania", "Albania",
"Algeria", "Algeria",
"Andorra", "Andorra",
"Angola", "Angola",
"Argentina", "Argentina",
"Armenia", "Armenia",
"Australia", "Australia",
"Austria", "Austria",
"Azerbaijan", "Azerbaijan",
"Bahrain", "Bahrain",
"Bangladesh", "Bangladesh",
"Belarus", "Belarus",
"Belgium", "Belgium",
"Belize", "Belize",
"Benin", "Benin",
"Bhutan", "Bhutan",
"Bolivia", "Bolivia",
"Bosnia and Herzegovina", "Bosnia and Herzegovina",
"Botswana", "Botswana",
"Brazil", "Brazil",
"Brunei", "Brunei",
"Bulgaria", "Bulgaria",
"Burkina Faso", "Burkina Faso",
"Burundi", "Burundi",
"Cambodia", "Cambodia",
"Cameroon", "Cameroon",
"Canada", "Canada",
"Chad", "Chad",
"Chile", "Chile",
"China", "China",
"Colombia", "Colombia",
"Comoros", "Comoros",
"Congo", "Congo",
"Costa Rica", "Costa Rica",
"Croatia", "Croatia",
"Cuba", "Cuba",
"Cyprus", "Cyprus",
"Czech Republic", "Czech Republic",
"Denmark", "Denmark",
"Djibouti", "Djibouti",
"Dominican Republic", "Dominican Republic",
"Ecuador", "Ecuador",
"Egypt", "Egypt",
"El Salvador", "El Salvador",
"Eritrea", "Eritrea",
"Estonia", "Estonia",
"Eswatini", "Eswatini",
"Ethiopia", "Ethiopia",
"Finland", "Finland",
"France", "France",
"Gabon", "Gabon",
"Gambia", "Gambia",
"Georgia", "Georgia",
"Germany", "Germany",
"Ghana", "Ghana",
"Greece", "Greece",
"Guatemala", "Guatemala",
"Guinea", "Guinea",
"Haiti", "Haiti",
"Honduras", "Honduras",
"Hungary", "Hungary",
"Iceland", "Iceland",
"India", "India",
"Indonesia", "Indonesia",
"Iran", "Iran",
"Iraq", "Iraq",
"Ireland", "Ireland",
"Israel", "Israel",
"Italy", "Italy",
"Jamaica", "Jamaica",
"Japan", "Japan",
"Jordan", "Jordan",
"Kazakhstan", "Kazakhstan",
"Kenya", "Kenya",
"Kuwait", "Kuwait",
"Kyrgyzstan", "Kyrgyzstan",
"Laos", "Laos",
"Latvia", "Latvia",
"Lebanon", "Lebanon",
"Liberia", "Liberia",
"Libya", "Libya",
"Lithuania", "Lithuania",
"Luxembourg", "Luxembourg",
"Madagascar", "Madagascar",
"Malawi", "Malawi",
"Malaysia", "Malaysia",
"Maldives", "Maldives",
"Mali", "Mali",
"Malta", "Malta",
"Mexico", "Mexico",
"Moldova", "Moldova",
"Monaco", "Monaco",
"Mongolia", "Mongolia",
"Morocco", "Morocco",
"Mozambique", "Mozambique",
"Myanmar", "Myanmar",
"Namibia", "Namibia",
"Nepal", "Nepal",
"Netherlands", "Netherlands",
"New Zealand", "New Zealand",
"Nicaragua", "Nicaragua",
"Niger", "Niger",
"Nigeria", "Nigeria",
"North Korea", "North Korea",
"Norway", "Norway",
"Oman", "Oman",
"Pakistan", "Pakistan",
"Panama", "Panama",
"Paraguay", "Paraguay",
"Peru", "Peru",
"Philippines", "Philippines",
"Poland", "Poland",
"Portugal", "Portugal",
"Qatar", "Qatar",
"Romania", "Romania",
"Russia", "Russia",
"Rwanda", "Rwanda",
"Saudi Arabia", "Saudi Arabia",
"Senegal", "Senegal",
"Serbia", "Serbia",
"Singapore", "Singapore",
"Slovakia", "Slovakia",
"Slovenia", "Slovenia",
"Somalia", "Somalia",
"South Africa", "South Africa",
"South Korea", "South Korea",
"Spain", "Spain",
"Sri Lanka", "Sri Lanka",
"Sudan", "Sudan",
"Sweden", "Sweden",
"Switzerland", "Switzerland",
"Syria", "Syria",
"Taiwan", "Taiwan",
"Tajikistan", "Tajikistan",
"Tanzania", "Tanzania",
"Thailand", "Thailand",
"Tunisia", "Tunisia",
"Turkey", "Turkey",
"Uganda", "Uganda",
"Ukraine", "Ukraine",
"United Arab Emirates", "United Arab Emirates",
"United Kingdom", "United Kingdom",
"United States", "United States",
"Uruguay", "Uruguay",
"Uzbekistan", "Uzbekistan",
"Venezuela", "Venezuela",
"Vietnam", "Vietnam",
"Yemen", "Yemen",
"Zambia", "Zambia",
"Zimbabwe" "Zimbabwe"
]; ];
void setSelectedCountry(String value) { void setSelectedCountry(String value) {
_selectedCountry = value; _selectedCountry = value;
@ -305,24 +301,24 @@ class ProfileDetailViewModel extends ReactiveViewModel
// Region // Region
List<String> getRegions() => [ List<String> getRegions() => [
'Addis Ababa', 'Addis Ababa',
'Afar', 'Afar',
'Amhara', 'Amhara',
'Benishangul-Gumuz', 'Benishangul-Gumuz',
'Central Ethiopia', 'Central Ethiopia',
'Dire Dawa', 'Dire Dawa',
'Gambela', 'Gambela',
'Harari', 'Harari',
'Oromia', 'Oromia',
'Sidama', 'Sidama',
'Somali', 'Somali',
'South Ethiopia', 'South Ethiopia',
'South West Ethiopia Peoples', 'South West Ethiopia Peoples',
'Tigray', 'Tigray',
]; ];
bool checkRegion({required String region,required String country}){ bool checkRegion({required String region, required String country}) {
if(country == 'Ethiopia'){ if (country == 'Ethiopia') {
return getRegions().contains(region); return getRegions().contains(region);
} }
return false; return false;

View File

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

View File

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

View File

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

View File

@ -2,8 +2,19 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
import '../../../app/app.locator.dart'; import '../../../app/app.locator.dart';
import '../../../services/url_launcher_service.dart';
import '../../common/app_constants.dart';
class TelegramSupportViewModel extends BaseViewModel { class TelegramSupportViewModel extends BaseViewModel {
// Dependency injection
final _navigationService = locator<NavigationService>(); final _navigationService = locator<NavigationService>();
final _urlLauncherService = locator<UrlLauncherService>();
// Launch telegram
Future<void> launchTelegram() =>
_urlLauncherService.launchUri(kTelegramSupport);
// Navigation
void pop() => _navigationService.back(); 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:flutter/material.dart';
import 'package:yimaru_app/ui/common/app_colors.dart'; import 'package:yimaru_app/ui/common/app_colors.dart';
import 'package:yimaru_app/ui/widgets/app_bar_pattern.dart';
import 'package:yimaru_app/ui/widgets/language_button.dart'; import 'package:yimaru_app/ui/widgets/language_button.dart';
class LargeAppBar extends StatelessWidget { class LargeAppBar extends StatelessWidget {
@ -18,12 +19,11 @@ class LargeAppBar extends StatelessWidget {
required this.showLanguageSelection}); required this.showLanguageSelection});
@override @override
Widget build(BuildContext context) => _buildAppBarWrapper(); Widget build(BuildContext context) => _buildStackWrapper();
Widget _buildAppBarWrapper() => Container( Widget _buildStackWrapper() => Container(
height: 125, height: 125,
width: double.maxFinite, width: double.maxFinite,
alignment: Alignment.bottomCenter,
decoration: const BoxDecoration( decoration: const BoxDecoration(
color: kcPrimaryColor, color: kcPrimaryColor,
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@ -31,6 +31,18 @@ class LargeAppBar extends StatelessWidget {
bottomRight: Radius.circular(24), 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), padding: const EdgeInsets.only(bottom: 25, right: 15),
child: _buildAppBarItems(), child: _buildAppBarItems(),
); );
@ -74,4 +86,6 @@ class LargeAppBar extends StatelessWidget {
Icons.close, Icons.close,
color: kcWhite, color: kcWhite,
); );
Widget _buildPattern() => const AppBarPattern();
} }

View File

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

View File

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

View File

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

View File

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

View File

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